Files
Messe-Lotse/plan/architecture-rewrite-1.0.md
T
2026-09-29 09:40:32 +00:00

82 KiB
Raw Blame History

goal, version, date_created, last_updated, owner, status, tags
goal version date_created last_updated owner status tags
Production-Ready Rewrite of 24HRS Messe-Lotse (Pimcore + React) 1.0 2026-06-09 2026-06-09 24HRS Team Planned architecture, rewrite, feature, pimcore, react

Introduction

Status: Planned

Full production rewrite of the 6,200-line monolithic index.html trade fair project management tool ("24HRS Messe-Lotse") into a multi-user web application backed by Pimcore (already in place) with a React + TypeScript frontend.

Architecture: React SPA served by Pimcore domain → Pimcore REST API (OAuth2) → MySQL + Gotenberg (PDF)

Data Model (4 entities):

fairLocation ──1→n── fair ──1→n── job ──n→1── customer
   (where)         (when)        (what)        (who)

1. Requirements & Constraints

  • REQ-001: Multi-user with OAuth2 authentication (Pimcore password grant)
  • REQ-002: Four roles with scoped visibility — Admin (all data, config, user management), Editor (all jobs across all customers, resource pool, workflow), Viewer (read-only across all jobs), Customer (restricted to jobs linked to their customer record; sees 4 of 8 tabs; cannot create/delete jobs or manage resources)
  • REQ-002a: Customer role access rules — editable: Tab 0 booth details (standType/size/height/hall/area/image/notes), Tab 1 briefing documents, Tab 4 checklist check/uncheck, Tab 5+6 stand photos + acceptance; read-only: fair/fairLocation info, workflow state, inherited dates; hidden: Tabs 2 Organisations, Tab 3 Produktion, Tab 7 Weekly; actions: save own editable fields, export PDF of own jobs, no delete/duplicate/create/transition
  • REQ-003: All file uploads stored in Pimcore DAM (not base64 in localStorage)
  • REQ-004: PDF export via Gotenberg (already running)
  • REQ-005: Frontend served from Pimcore domain (no CORS, session cookie reuse)
  • REQ-006: Feature parity with existing index.html — all 8 tabs + dashboard + calendar
  • REQ-007: Symfony Workflow-driven phase progression on job entity — 8-state state machine (Angebot → Planung → Organisation → Produktion → Aufbau → Messe → Abbau → Abgeschlossen) with transition guards
  • REQ-008: Normalized 4-entity data model — fairLocation (venue), fair (trade fair event), job (booth project), customer (company); one fair contains many jobs, one customer can have multiple jobs at the same fair
  • REQ-009: Date inheritance — job inherits setupStart/End, eventStart/End, teardownStart/End from its parent fair, with per-job override capability
  • REQ-010: "Fair Detail" view — the primary "todo" grouping: open a fair → see all its jobs across all customers, combined calendar, shared deadlines, bulk PDF export
  • REQ-011: Resource pool management — 11 entities (WorkerRole, Worker, PoolVehicle, Equipment, Accommodation, WorkerDemand, WorkerAssignment, PoolVehicleAssignment, EquipmentAssignment, AccommodationAssignment, Travel); plan resource needs via WorkerDemand (role + quantity), fill with WorkerAssignment (specific worker + dates); fair-level resource view with gap analysis (needed − assigned); worker-level travel management (one-way or round-trip per fair); replaces all per-job ad-hoc team/vehicle/hotel lists
  • REQ-012: AI document processing skills — pluggable AI skills triggered on asset upload to Pimcore DAM; extract information from uploaded documents, compare with stored job data, surface suggestions for auto-fill, validation warnings, and data enrichment; skills include: document classifier, contact extractor, deadline detector, stand data extractor, confirmation parser, checklist generator, brand consistency checker, material list extractor, acceptance report generator, setup progress analyzer, weekly protocol summarizer; all AI suggestions stored as non-destructive metadata (user accepts/ignores); phased rollout Tiers 1→2→3
  • REQ-013: Tiered document storage — documents stored at the entity level matching their scope (customer/fair/job), not only on job. customer.brandDoc, consultationDocs, contractDocs for company-wide documents. fair.constructionBriefing, floorPlan, regulations, protocols for fair-wide documents. Job retains booth-specific documents (confirmations, photos, acceptance, signature). AI DocumentScopeDetector identifies scope on upload and suggests relocation if mismatched. Job detail shows merged view of all three levels.
  • CON-001: Must run in existing Docker Compose environment
  • CON-002: No legacy data migration needed (greenfield)
  • CON-003: Pimcore ≥ 11.x, Gotenberg, and OAuth2 already in place
  • SEC-001: Booking platform passwords stored encrypted (Pimcore encrypted field type)
  • SEC-002: All API routes protected by OAuth2, role-checked per endpoint
  • GUD-001: React 18 + TypeScript + Vite + TanStack Query + Zustand + shadcn/ui

2. Implementation Steps

Implementation Phase 1 — Pimcore Bundle & Data Model

  • GOAL-001: All backend capabilities (4 entities, relations, API, services) exist before any frontend work begins.

1.1 Data Object Classes

Task Description
TASK-001 Scaffold MessebauBundle in bundles/MessebauBundle/ with Symfony bundle class, services.yaml, routes.yaml
TASK-002 Create Data Object FairLocation (venue) — text fields: name, address, googleMapsUrl; contact fields: contactName, contactPhone, contactEmail; textarea: notes
TASK-003 Create Data Object Customer (company) — text fields: company (name), address; contact fields: contactName, contactPhone, contactEmail; asset fields: brandDoc (document, company-wide brandbook/CI guidelines), consultationDocs (document gallery, multi-fair planning/consultation docs), contractDocs (document gallery, framework agreements); textarea: notes
TASK-004 Create Data Object Fair (trade fair event) — text fields: name, hallsUsed; relation fields: fairLocation (1→1 to FairLocation); datetime fields: setupStart, setupEnd, eventStart, eventEnd, teardownStart, teardownEnd; asset fields: constructionBriefing (document gallery, multi-booth briefings like CFC 2026), floorPlan (document gallery, hall-level plans + venue maps), regulations (document gallery, venue regs + fire safety), protocols (document gallery, fair-wide weekly protocols); textarea: notes
TASK-005 Create Data Object Job (booth project) — the core entity, replaces old MessebauJob. Location fields: standType (select: Systemstand/Reihstand/Eckstand/Kopfstand/Blockstand/Inselstand), standSize, standHeight, hall (e.g. "Hall 3 / Booth C12"), area, project. Workflow field: workflowState (input, managed by Symfony Workflow). Relation fields: customer (n→1 to Customer), fair (n→1 to Fair). Date overrides (nullable; if null, inherit from fair): setupStartOverride, setupEndOverride, eventStartOverride, eventEndOverride, teardownStartOverride, teardownEndOverride. Notes: textarea
TASK-006 Create field collections for Job: BookingPlatformInfo (portalLink, email, password[encrypted], deadline), PowerInfo (kw, distributor, deadline), WlanInfo (type, deadline), CleaningInfo (deadline), RiggingInfo (supplierFirstname, supplierLastname, supplierEmail, supplierPhone, messeFirstname, messeLastname, messeEmail, messePhone, deadline), DruckproduktionInfo (qm, blockout, forexplatten, deadline), CateringInfo (baristaEnabled, baristaPackage, days, drinksEnabled, drinksDetails, foodEnabled, foodDetails), CostEstimate (standConstruction, activation, furniture, logistics — all integer, currency EUR; used by BudgetExtractor AI skill)
TASK-007 Create related Data Objects (all with 1:n relation to Job): Material (type[flooring/standsystem/hardware/furniture], name, quantity, notes), ChecklistItem (text, checked, order). Note: per-job team, vehicles, and hotels are replaced by the resource pool (see 1.4)
TASK-008 Add asset/image/document fields to Job: standImage (image), standGallery (image gallery), briefingCustomerDoc (document, optional: per-job override; if null, shows customer.consultationDocs), briefingPlanDoc (document, optional: per-job override; if null, shows fair.floorPlan), briefingAdditionalDoc (document), powerConfirmation (document), wlanConfirmation (document), cleaningConfirmation (document), riggingQuote (document), riggingDrawing (document), riggingMesseConfirmation (document), standApprovalDoc (document), acceptancePhotos (image gallery), acceptanceSignature (image), weeklyProtocols (document gallery). Note: briefingBrandDoc moved to customer.brandDoc — brandbook is company-wide, not per-job

1.2 Workflow

Task Description
TASK-009 Create Symfony Workflow config in bundles/MessebauBundle/Resources/config/workflows/job.yml — 8 places (angebot, planung, organisation, produktion, aufbau, messe, abbau, abgeschlossen); initial marking: angebot; transitions with guards: angebot→planung requires customer and fair relations set, planung→organisation requires briefingCustomerDoc uploaded, organisation→produktion requires power/wlan/cleaning deadlines set if their checkboxes enabled, produktion→aufbau requires all checklist items checked, abbau→abgeschlossen requires acceptanceSignature+acceptanceDate set; date-based transitions (aufbau→messe, messe→abbau) auto-fire via cron checking effective dates (override or inherited from fair)
TASK-010 Create WorkflowController — GET /api/messebau/jobs/{id}/workflow returns current place, available transitions (with guard status: allowed/blocked/reason), transition history; POST /api/messebau/jobs/{id}/workflow/{transition} applies a manual transition
TASK-011 Create WorkflowTransitionListener — on workflow.enter: log timestamp to job's transitionHistory (JSON array); on workflow.enter.aufbau: auto-generate PDF export for setup team; on workflow.enter.messe: send customer email with booth details; on workflow.enter.abgeschlossen: mark job as read-only

1.3 API Controllers

Task Description
TASK-012 Create FairLocationController — GET /api/messebau/fairlocations (list), GET/POST/PUT/DELETE /api/messebau/fairlocations/{id}
TASK-013 Create CustomerController — GET /api/messebau/customers (list, searchable by company name), GET/POST/PUT/DELETE /api/messebau/customers/{id}; GET /api/messebau/customers/{id}/jobs lists all jobs for this customer
TASK-014 Create FairController — GET /api/messebau/fairs (list, filterable by fairLocation, date range), GET/POST/PUT/DELETE /api/messebau/fairs/{id}; GET /api/messebau/fairs/{id}/jobs lists all jobs for this fair (the "todo" view); fair serialization includes fairLocation name inline
TASK-015 Create JobController — GET /api/messebau/jobs (paginated, filterable by workflowState/fair/customer/search), GET /api/messebau/jobs/{id} (full serialization with relations + asset URLs + resolved effectiveDates), POST /api/messebau/jobs (create — requires customer and fair relation IDs, initializes default checklist items), PUT /api/messebau/jobs/{id} (full update), PATCH /api/messebau/jobs/{id} (partial update), DELETE /api/messebau/jobs/{id}
TASK-016 Create AssetController — POST /api/messebau/jobs/{id}/upload/{fieldName} (validate file type+size, create Pimcore Asset, link to field), DELETE /api/messebau/jobs/{id}/asset/{fieldName}. Also handles customer and fair entity uploads (brandDoc, consultationDocs, contractDocs, constructionBriefing, floorPlan, regulations, protocols) via generalized endpoint POST /api/messebau/{entity}/{id}/upload/{fieldName}
TASK-017 Create CalendarController — GET /api/messebau/calendar?year=&month=&fairId= — aggregate effective dates (override or inherited from fair) plus all deadline fields across jobs; return flat event list with type+color+jobId+fairId; filterable by fairId
TASK-018 Create JobDuplicateService — deep clone Job data object (reset workflowState to angebot, clear acceptance data), copy all related objects (materials, checklist), copy all assets in DAM, append "(Kopie)" to project field, re-link to same customer and fair, generate new ID
TASK-019 Create JobExportService — accepts job ID, loads full data with joined customer/fair/fairLocation, resolves effective dates, renders Twig template (templates/messebau/pdf/export.html.twig), POSTs HTML to Gotenberg, returns PDF binary with filename Jobzettel-{customer.company}-{fair.name}.pdf
TASK-020 Create JobNotificationListener — listen to dataobject.postUpdate, check changed deadline fields, dispatch async messages for email reminders at T-7, T-3, T-1 days
TASK-021 Create FairExportService — accepts fair ID, loads all jobs, renders aggregated Twig template (all jobs for that fair), POSTs to Gotenberg, returns combined PDF. Endpoint: GET /api/messebau/fairs/{id}/export-pdf
TASK-022 Configure OAuth2 firewall — protect /api/messebau/* with pimcore.api firewall
TASK-023 Define roles and scoping — ROLE_MESSEBAU_CUSTOMER (own jobs only, limited fields, 4/8 tabs, no delete/duplicate/create/transition), ROLE_MESSEBAU_VIEWER (GET only across all jobs), ROLE_MESSEBAU_EDITOR (CRUD across all jobs + resource pool), ROLE_MESSEBAU_ADMIN (full access + user management + delete). Enforce in controller method annotations. Add API scoping base class: when ROLE_MESSEBAU_CUSTOMER, read user.messebauCustomer relation and auto-filter all job queries by customer = user.messebauCustomer; 403 on cross-company access; 403 on all resource/pool endpoints; fair/fairLocation read-only GET
TASK-023a Add messebauCustomer field to Pimcore User class (n→1 relation to Customer DO, nullable). Admin sets this during user creation to link a login to a customer company. Customer users without this relation get 403 on all endpoints.
TASK-024 Write PHPUnit tests: FairLocationControllerTest, CustomerControllerTest, FairControllerTest, JobControllerTest (including customer scoping: verify 403 on other company's jobs, fields limited per role), JobDuplicateServiceTest (clone integrity with preserved relations), JobExportServiceTest, FairExportServiceTest, CalendarControllerTest, WorkflowTest, WorkflowTransitionListenerTest
TASK-025 Register all API routes in bundles/MessebauBundle/Resources/config/routes.yaml with messebau_api prefix

1.4 Resource Pool Entities

Task Description
TASK-026 Create Data Object WorkerRole (role category) — text field: name (e.g. "Barista", "Rigger", "LorryDriver", "FairWorker", "Electrician", "Cleaner")
TASK-027 Create Data Object Worker (person pool) — text fields: firstName, lastName, email, phone; relation: role (n→1 to WorkerRole); select field: state (internal/hired/toHire); textarea: notes
TASK-028 Create Data Object PoolVehicle (truck pool) — text fields: name, type, licensePlate; select field: state (owned/hired/toHire); textarea: notes
TASK-029 Create Data Object Equipment (special items pool) — text fields: name, description; select field: state (owned/rented/toRent); textarea: notes
TASK-030 Create Data Object WorkerDemand (planning: "we need X people of this role") — relation fields: fair→Fair (required), job→Job (nullable), workerRole→WorkerRole (required); integer field: quantity; textarea: notes
TASK-031 Create Data Object WorkerAssignment (execution: "Anna fills that slot") — relation fields: fair→Fair (required), job→Job (nullable), worker→Worker (required); datetime fields: startDate, endDate; textarea: notes
TASK-032 Create Data Object PoolVehicleAssignment — relation fields: fair→Fair (required), job→Job (nullable), poolVehicle→PoolVehicle (required); datetime fields: startDate, endDate; textarea: notes
TASK-033 Create Data Object EquipmentAssignment — relation fields: fair→Fair (required), job→Job (nullable), equipment→Equipment (required); datetime fields: startDate, endDate; textarea: notes
TASK-033a Create Data Object Accommodation (hotel/apartment pool) — text fields: name, address, bookingReference; select field: type (hotel/apartment/other); textarea: notes
TASK-033b Create Data Object AccommodationAssignment — relation fields: fair→Fair (required), accommodation→Accommodation (required), worker→Worker (nullable); date fields: checkIn, checkOut; textarea: notes. Multiple workers can share the same accommodation at a fair
TASK-033c Create Data Object Travel (per worker per fair) — relation fields: worker→Worker (required), fair→Fair (required); select field: type (oneWay/roundTrip); text fields: origin, destination, transportMethod, bookingReference; datetime fields: departureDateTime, arrivalDateTime; datetime fields: returnDepartureDateTime (nullable), returnArrivalDateTime (nullable); textarea: notes
TASK-034 Create WorkerRoleController — GET /api/messebau/workerroles (list), GET/POST/PUT/DELETE /api/messebau/workerroles/{id}
TASK-035 Create WorkerController — GET /api/messebau/workers (list, filterable by ?roleId=&state=), GET/POST/PUT/DELETE /api/messebau/workers/{id}
TASK-036 Create PoolVehicleController — GET /api/messebau/poolvehicles (list, filterable by ?state=), GET/POST/PUT/DELETE /api/messebau/poolvehicles/{id}
TASK-037 Create EquipmentController — GET /api/messebau/equipment (list, filterable by ?state=), GET/POST/PUT/DELETE /api/messebau/equipment/{id}
TASK-038 Create FairResourceController — GET /api/messebau/fairs/{id}/resources returns all demands+assignments grouped by type with gap computed (needed − assigned); POST/PUT/DELETE /api/messebau/fairs/{id}/demands manages demand slots; POST/PUT/DELETE /api/messebau/fairs/{id}/assignments manages worker/poolVehicle/equipment assignments
TASK-039 Create JobResourceController — GET /api/messebau/jobs/{id}/resources returns demands+assignments scoped to this job
TASK-039a Create AccommodationController — GET /api/messebau/accommodations (list), GET/POST/PUT/DELETE /api/messebau/accommodations/{id}
TASK-039b Create FairAccommodationController — GET /api/messebau/fairs/{id}/accommodations returns accommodations + assignments + occupancy for this fair; POST/PUT/DELETE /api/messebau/fairs/{id}/accommodations/{accId}/assign manages check-in/check-out
TASK-039c Create WorkerTravelController — GET /api/messebau/workers/{id}/travel lists all travel for this worker (filterable by ?fairId=); POST/PUT/DELETE /api/messebau/workers/{id}/travel manages travel records
TASK-040 Write PHPUnit tests: WorkerRoleControllerTest, WorkerControllerTest, PoolVehicleControllerTest, EquipmentControllerTest, AccommodationControllerTest, FairAccommodationControllerTest, WorkerTravelControllerTest, FairResourceControllerTest (demand fill calculation, gap computation, assignment scoping), JobResourceControllerTest

Workflow State Machine Diagram

     ┌──────────┐                     Progress   Color
     │  Angebot  │  ← Job created        12%      gray
     └────┬─────┘
          │ Guard: customer + fair relations set
     ┌────▼─────┐
     │ Planung   │  ← Briefing uploads    25%      blue
     └────┬─────┘
          │ Guard: briefingCustomerDoc uploaded
     ┌────▼─────┐
     │ Organisation│ ← Services booked    37%      teal
     └────┬─────┘
          │ Guard: power/wlan/cleaning deadlines set (if enabled)
     ┌────▼─────┐
     │ Produktion│ ← Materials+printing   50%      indigo
     └────┬─────┘
          │ Guard: all checklist items checked
     ┌────▼─────┐
     │  Aufbau   │  ← effective setup dates 62%      orange
     └────┬─────┘
          │ Auto: effective setupEnd reached (cron)
     ┌────▼─────┐
     │  Messe    │  ← effective event dates 75%      yellow
     └────┬─────┘
          │ Auto: effective eventEnd reached (cron)
     ┌────▼─────┐
     │  Abbau    │  ← effective teardown dates 87%      red
     └────┬─────┘
          │ Guard: acceptanceSignature + acceptanceDate set
     ┌────▼─────┐
     │ Abgeschl. │  ← Archive, read-only  100%      green
     └──────────┘

Effective dates: Each job resolves dates as job.xxxOverride ?? fair.xxx. The workflow, calendar, and PDF export all use resolved effective dates.

Transition types:

  • Manual (user clicks button in UI): angebot→planung, planung→organisation, organisation→produktion, produktion→aufbau, abbau→abgeschlossen
  • Automatic (cron checks effective dates): aufbau→messe (when effective setupEnd < now), messe→abbau (when effective eventEnd < now)
  • Optional rollback: Abgeschlossen jobs can be reopened to Abbau by admin only

Transition history stored as JSON array on the job object: [{from: "planung", to: "organisation", timestamp: "2026-06-09T14:30:00Z", user: "max.mustermann"}]

Implementation Phase 2 — React Skeleton & Auth

  • GOAL-002: Authenticated React app served from Pimcore domain with routing in place.
Task Description Completed Date
TASK-016 Scaffold Vite + React + TypeScript project in frontend/ with Tailwind CSS v3 and shadcn/ui initialized
TASK-017 Configure vite.config.ts — build output to ../public/build/messebau/, base path /build/messebau/, proxy /api to Pimcore in dev mode
TASK-018 Create src/api/client.ts — Axios instance with baseURL: '/api/messebau', request interceptor to attach Authorization: Bearer {accessToken}, response interceptor for 401 → attempt refresh token → retry original request; implement refresh token queue to prevent concurrent refresh race conditions
TASK-019 Create src/stores/authStore.ts — Zustand store: accessToken, refreshToken, user (id, username, roles), login(username, password) (POST to /oauth2/token with password grant), logout(), refreshAccessToken(), persist tokens to sessionStorage (not localStorage for security)
TASK-020 Create src/stores/uiStore.ts — Zustand store: currentView (dashboard/calendar/detail), currentJobId, toastQueue[], sidebarCollapsed
TASK-021 Create src/components/auth/LoginPage.tsx — username + password form, calls authStore.login(), error display, redirects to dashboard on success; route at /login
TASK-022 Create src/components/auth/ProtectedRoute.tsx — checks auth store for token, redirects to /login if missing, renders <Outlet /> if authenticated
TASK-023 Create src/components/layout/AppShell.tsx — flex layout: <Sidebar> (260px fixed) + <main> (flex-1, 32px padding); wraps all authenticated routes
TASK-024 Create src/components/layout/Sidebar.tsx — "24HRS" logo + "Messe-Lotse" subtitle (yellow on black); nav items (role-dependent): Dashboard, Kalender always shown; "Ressourcen" (submenu: Worker Roles, Worker Pool, Truck Pool, Equipment Pool, Accommodations) hidden for customer; "Admin" hidden for customer/viewer; active state via useLocation(); logout button at bottom
TASK-024a Create src/hooks/usePermissions.ts — reads role from authStore.user.roles; returns boolean flags: canEdit, canDelete, canCreateJob, canDuplicateJob, canTransitionWorkflow, canManageResources, canSeeOrgTab, canSeeProduktionTab, canSeeWeeklyTab, canSeeCustomerTab, canAddChecklistItems, isCustomer; all components import this hook to conditionally render UI elements
TASK-025 Set up react-router-dom in src/App.tsx — routes: /login → LoginPage, / → ProtectedRoute → AppShell → Dashboard, /fair/:id → FairDetail (the "todo" view), /customer/:id → CustomerDetail, /jobs/:id → JobDetail, /calendar → Calendar, /calendar?fairId=:id → Calendar scoped to fair; 404 catch-all
TASK-026 Create Pimcore controller MessebauController::appAction() — serves public/build/messebau/index.html with Twig; configure route /messebau/{react} with react as optional catch-all parameter for SPA client-side routing
TASK-027 Write Vitest tests: authStore.test.ts (login, logout, token refresh flow), client.test.ts (token attachment, 401 refresh, request retry)

Implementation Phase 3 — Dashboard, Hierarchy & Job CRUD

  • GOAL-003: Dashboard with hierarchical navigation (fair → jobs), full job lifecycle, and entity creation flow.
Task Description Completed Date
TASK-028 Create src/types/job.ts — TypeScript interfaces: FairLocation, Customer, Fair, Job (with effectiveDates resolved object), Vehicle, Hotel, TeamMember, Material, ChecklistItem, BookingPlatformInfo, PowerInfo, WlanInfo, CleaningInfo, RiggingInfo, DruckproduktionInfo, CateringInfo, Acceptance, Briefing, LeadMagnets, JobStats, WorkflowState (place, availableTransitions, history)
TASK-029 Create src/api/fairLocations.ts + src/api/customers.ts + src/api/fairs.ts + src/api/jobs.ts — typed API functions for all 4 entity CRUD operations; jobs API supports filters ?fairId=&customerId=&workflowState=&search=
TASK-030 Create src/hooks/useFairs.ts + src/hooks/useJobs.ts + src/hooks/useJob.ts — TanStack Query hooks; useFairs is the primary list on the dashboard; useJobs(fairId?) for fair-detail view
TASK-031 Create src/components/dashboard/StatsGrid.tsx — stat cards: Gesamt (total jobs), Laufende Messen (fairs with active jobs), Diese Woche (jobs in setup/messe/teardown this week)
TASK-032 Create src/components/dashboard/FairCard.tsx — card per fair showing fair name, fairLocation name, date range, job count, combined progress; clicking navigates to /fair/{id} (the "todo" view)
TASK-033 Create src/components/dashboard/FairCardGrid.tsx — responsive grid of FairCards; search bar filters by fair name or fairLocation name
TASK-034 Create src/components/dashboard/EmptyState.tsx — for internal roles: "Keine Messen vorhanden" + "Neue Messe anlegen" button; for customer role: "Keine Projekte vorhanden — 24HRS wird Ihre Messeaufträge hier bereitstellen" (no create button)
TASK-035 Create src/components/fair/FairDetailView.tsx — fair header (name, location, dates), stats for this fair (total jobs, open jobs), action buttons (edit fair, bulk PDF export), then <JobCardGrid> filtered to this fair; "Neuen Job hinzufügen" button
TASK-036 Create src/components/job/CreateJobFlow.tsx — wizard: Step 1: select or create Customer (combobox with search + "Neuen Kunden anlegen" option), Step 2: select or create Fair (combobox grouped by fairLocation + "Neue Messe anlegen" option), Step 3: enter booth details (project, standType, standSize, hall). After creation, navigate to job detail
TASK-037 Create src/components/job/JobCard.tsx — card with stand image, customer.company, fair.name, hall, workflow progress bar (12%→100%) with phase label; onClick navigates to /jobs/{id}
TASK-038 Create src/components/job/JobCardGrid.tsx — responsive grid, maps jobs to <JobCard>
TASK-039 Create src/components/job-detail/DetailHeader.tsx — black bar: breadcrumb (fair.name → customer.company → job.project), back button, action buttons gated by usePermissions: save (all except viewer), duplicate (editor/admin only), export job PDF (all), delete (admin only); customer sees save + export only, no delete/duplicate/transition; delete shows confirm dialog
TASK-040 Create src/components/job-detail/TabContainer.tsx — dynamic tab list based on role: internal sees 8 tabs; customer sees 4 tabs (Tab 0 Stammdaten, Tab 1 Briefing, Tab 4 Dokumente, Tab 5+6 Standfotos+Standabnahme); tabs 2/3/7 hidden for customer; tab visibility controlled by usePermissions hook; active tab stored in search params (?tab=0)
TASK-041 Implement save — updateJob() mutation collects all form data, shows toast
TASK-042 Implement delete — confirm dialog, deleteJob() mutation, navigate to parent fair detail
TASK-043 Implement duplicate — duplicateJob() mutation, creates new job linked to same customer and fair, navigate to new job
TASK-044 Write Playwright test: create-fair-and-jobs.spec.ts — create fairLocation → create customer → create fair → create two jobs for different customers at same fair → verify both appear in fair detail
TASK-045 Write Playwright test: duplicate-delete.spec.ts — open job, duplicate, verify "(Kopie)" in project, navigate to fair detail, delete original, verify gone

Implementation Phase 4 — Tab 0: Master Data & Scheduling

  • GOAL-004: Stammdaten & Termine tab with relation display, date inheritance, and stand image upload.
Task Description Completed Date
TASK-046 Create src/components/job-detail/tabs/MasterDataTab.tsx — two-column form grid using react-hook-form + zod validation
TASK-047 Implement entity selectors section — Customer + Fair + FairLocation shown read-only (for customer role: fixed display, no "Ändern" button; for internal: editable combo boxes); all roles see fair/fairLocation info + Google Maps link
TASK-048 Implement Stammdaten section: Projekt (text, editable by all), Standart (select, editable by all), Standgröße (text, editable by all), Standhöhe (number, editable by all), Halle/Stand (text, editable by all), Standfläche m² (number, editable by all)
TASK-049 Implement inherited Zeitplanung section: show fair's dates as read-only reference values; for internal roles: per-job override fields (nullable datetime-local); for customer role: no overrides, only read-only display of effective dates
TASK-050 Implement workflow state section — read-only badge showing current phase with color; for internal roles: transition buttons (grayed out if guard blocks, tooltip explains why blocked); for customer role: no transition buttons
TASK-051 Implement Messestand-Bild section: file input, image preview, download/remove buttons
TASK-052 Implement Notizen textarea (full-width, min-height 100px)
TASK-053 Write Playwright test: masterdata-tab.spec.ts — verify customer/fair/fairLocation display correctly; override setup date, save, reload, verify override persisted; verify inherited dates shown when override cleared

Implementation Phase 5 — Tab 1: Briefing (Merged View)

  • GOAL-005: Briefing tab shows documents from all three scope levels (customer → fair → job), with scope badges and inline AI scope detection.
Task Description Completed Date
TASK-051 Create src/components/job-detail/tabs/BriefingTab.tsx — 3 collapsible sections with scope badges: "☰ Kunde", "🌐 Messe", "📋 Job"; each section shows relevant documents from that entity level, merged into a single view
TASK-052 Implement Customer Documents section — shows customer.brandDoc (with download/delete), customer.consultationDocs gallery, customer.contractDocs gallery; each with [Add Document] button (uploads to customer entity, not job); AI scope badge if detected scope differs from current section
TASK-053 Implement Fair Documents section — shows fair.constructionBriefing gallery, fair.floorPlan gallery, fair.regulations gallery, fair.protocols gallery; each with [Add Document] button (uploads to fair entity); multi-booth briefings are visible to all jobs of this fair
TASK-054 Implement Job Documents section — shows job.briefingCustomerDoc (override, with "inherited from customer" indicator if null), job.briefingPlanDoc (override, with "inherited from fair" indicator if null), job.briefingAdditionalDoc; [Upload] buttons for job-level overrides
TASK-055 Implement scope notification on upload — when a job-level briefing upload is classified as multi-fair or multi-booth by DocumentScopeDetector, show: "⚠️ This document references X fairs / Y booths. Move to customer/fair level?" with [Move to customer] [Move to fair] [Keep here]
TASK-056 Add info banner at bottom — "Hinweis: Dokumente können auf Kunden-, Messe- oder Job-Ebene gespeichert werden. Scoped-AI erkennt automatisch die richtige Ebene."
TASK-057 Write Playwright test: briefing-tab.spec.ts — upload brandbook to customer level → verify visible in all jobs for that customer; upload multi-booth briefing to a job → verify scope mismatch detected → move to fair → verify all jobs now see document; upload job-specific briefing → verify shows only on that job

Implementation Phase 6 — Tab 2: Organisation

  • GOAL-006: Organisation tab with booking platform, services, rigging, hotels, and resource assignment summary (replaces old per-job ad-hoc team/vehicle lists — now managed via the resource pool at fair level).
Task Description Completed Date
TASK-058 Create src/components/job-detail/tabs/OrganisationTab.tsx — multiple collapsible/section blocks
TASK-059 Implement Buchungsplattform section — checkbox "Buchungsplattform vorhanden" toggles fields: Portal-Link (url), E-Mail (email), Passwort (password), Bestelldeadline (date)
TASK-060 Implement Strombuchung section — checkbox "Strom gebucht" toggles: Leistung kW (number), Stromverteiler (text), Bestelldeadline (date), Buchungsbestätigung PDF upload+preview
TASK-061 Implement WLAN section — checkbox "WLAN gebucht" toggles: Art (text), Bestelldeadline (date), Buchungsbestätigung PDF upload+preview
TASK-062 Implement Reinigungsservice section — checkbox "Reinigungsservice gebucht" toggles: Bestelldeadline (date), Buchungsbestätigung PDF upload+preview
TASK-063 Implement Rigging/Truss section — checkbox "Rigging/Truss vorhanden" toggles: Lieferant Kontaktdaten (Vorname, Nachname, E-Mail, Telefon), Bestelldeadline (date), Dokumente (Angebot PDF, Technische Zeichnung PDF, Auftragsbestätigung Messe PDF — each with upload+preview), Messe Kontaktdaten (Vorname, Nachname, E-Mail, Telefon)
TASK-064 Implement Standfreigabe section — checkbox "Standfreigabe vorhanden" toggles: Bestelldeadline (date), Freigabedokument PDF upload+preview
TASK-065 Implement resource assignment summary — read-only display of workers/vehicles/equipment assigned to this job (fetched via GET /api/messebau/jobs/{id}/resources), grouped by type; each item shows: name, role/type, state badge, dates; "[Manage all resources for this fair →]" link to fair Resources tab
TASK-066 Implement accommodation + travel summary — read-only display of accommodations assigned to workers at this fair (from FairAccommodationController) and travel records for workers on this job (from WorkerTravelController); "[Manage accommodation & travel →]" link to fair Resources tab

Implementation Phase 7 — Tab 3: Produktion

  • GOAL-007: Production tab with materials, printing, catering, lead magnets.
Task Description Completed Date
TASK-068 Create src/components/job-detail/tabs/ProduktionTab.tsx
TASK-069 Implement material lists — 4 sections: Bodenbeläge, Standsystem/Messebau-Elemente, Hardware/Technik, Möbel & Ausstattung; each with add button and list of items (name, quantity, notes); addMaterial(type) creates new Material in the list
TASK-070 Implement Druckproduktion section — checkbox toggles: Quadratmeter m² (number), Blockout (text), Anzahl Forexplatten (number), Bestelldeadline (date)
TASK-071 Implement Catering section — Barista-Service checkbox toggles: Service-Paket (select: 1 PAX 150/300, 2 PAX 150/300), Anzahl Messetage (number); Catering-Personal: displays workers assigned to this job with role=Barista (from GET /api/messebau/jobs/{id}/resources), with button to create WorkerDemand for missing roles; Getränke checkbox toggles textarea; Food checkbox toggles textarea
TASK-072 Implement Leadmagneten section — 9 checkboxes in 2-column grid: Flyer/Broschüren, Giveaways, Tablet, Badge-Scanner, Gewinnspiel, Produkt-Demos, KI Fotobox, Vending Machine, Greif-Arm

Implementation Phase 8 — Tab 4, 5, 6, 7: Documents, Photos, Acceptance, Weekly

  • GOAL-008: Remaining tabs — checklist, photo gallery, acceptance with signature, weekly protocols.
Task Description Completed Date
TASK-073 Create src/components/job-detail/tabs/DokumenteTab.tsx — checklist with 8 default items; each item: checkbox + label with strikethrough when checked; role-gated: internal roles can add/remove checklist items; customer role can only check/uncheck existing items (no add/delete via usePermissions.canAddChecklistItems); TanStack Query optimistic update on toggle
TASK-074 Create src/components/job-detail/tabs/StandfotosTab.tsx — upload zone (dashed border, drag-and-drop support, accepts image/* multiple, max 10 files 2 MB each); gallery grid (thumbnails 200px height, object-fit cover); each item has download + delete buttons; click opens <Lightbox> for full-size view with prev/next navigation
TASK-075 Create src/components/shared/Lightbox.tsx — portal-based (ReactDOM.createPortal), dark overlay, centered image, close button (top-right), prev/next arrow buttons, keyboard navigation (arrow keys, escape), click-outside-to-close, swipe gesture on mobile
TASK-076 Create src/components/job-detail/tabs/StandabnahmeTab.tsx — acceptance photo gallery (same pattern as Standfotos but separate asset field), signature pad using react-signature-canvas (600x200 canvas, clear + save buttons, "Unterschrift gespeichert" indicator), remarks textarea, Abnahmedatum (datetime-local)
TASK-077 Create src/components/job-detail/tabs/WeeklyTab.tsx — multi-file upload for PDFs (accept .pdf, multiple), list of uploaded protocols with filename, size, download button, delete button; empty state with PDF icon

Implementation Phase 9 — Calendar View

  • GOAL-009: Month-view calendar with color-coded job events, fair-scoped filtering, effective date resolution.
Task Description Completed Date
TASK-078 Create src/hooks/useCalendar.ts — TanStack Query hook fetching /api/messebau/calendar?year=&month=&fairId= for current month/year; store current month/year/fairId in local state; if viewing from fair detail, fairId is set; from global calendar, fairId is null (show all)
TASK-079 Create src/components/calendar/CalendarToolbar.tsx — prev/next month buttons, month-year heading, "Heute" button, fair selector dropdown (filter calendar to one fair), color legend (Aufbau=blue, Messe=yellow, Abbau=red, Deadline=orange)
TASK-080 Create src/components/calendar/CalendarGrid.tsx — 7-column grid, day headers (Mo-So), correct day cells, leading/trailing days from adjacent months grayed out
TASK-081 Create src/components/calendar/CalendarDay.tsx — day number (bold+today border if today), event pills: setup=#3498db, event=#FFE000+black text+bold, teardown=#e74c3c, deadline=#FF6B35+bold+border-left; each pill shows truncated text with customer+fair name, click navigates to /jobs/{jobId}
TASK-082 Wire calendar events — each job produces up to 4 event types using effective dates (override or inherited from fair); events spanning multiple days render on each day in the span

Implementation Phase 10 — PDF Export

  • GOAL-010: Server-side PDF generation via Gotenberg.
Task Description Completed Date
TASK-083 Add job PDF export endpoint — GET /api/messebau/jobs/{id}/export-pdf invokes JobExportService, returns PDF with Content-Disposition: attachment
TASK-083a Add fair bulk PDF export — button on FairDetailView triggers GET /api/messebau/fairs/{id}/export-pdf via FairExportService, returns combined PDF of all jobs for that fair
TASK-084 Create templates/messebau/pdf/export.html.twig — print-optimized HTML: 24HRS header, customer.company + fair.name + fairLocation.name, info grid (stand type, size, hall, effective dates), material tables, checklist with checkmarks, acceptance info, signature image; CSS with @page { margin: 20mm }, yellow accents, Inter font, page-break-inside: avoid
TASK-085 Add "PDF Export" button handler in DetailHeader.tsx — calls API, receives blob, creates download link, triggers click, shows toast

Implementation Phase 10a — Fair Resources Tab & Worker Management

  • GOAL-010a: Fair-level resource pool with demand planning, worker/vehicle/equipment/accommodation assignment, travel management, and fill-gap visualization.
Task Description Completed Date
TASK-R01 Create src/types/resources.ts — interfaces: WorkerRole, Worker, PoolVehicle, Equipment, Accommodation, WorkerDemand, WorkerAssignment, PoolVehicleAssignment, EquipmentAssignment, AccommodationAssignment, Travel, FairResources (all demands + assignments grouped by type with gap)
TASK-R02 Create src/api/workerroles.ts + src/api/workers.ts + src/api/poolvehicles.ts + src/api/equipment.ts + src/api/accommodations.ts + src/api/travel.ts + src/api/fairResources.ts — typed API functions for all 11 resource entities
TASK-R03 Create src/hooks/useFairResources.ts — TanStack Query hook fetching GET /api/messebau/fairs/{id}/resources, returns grouped resource view with gap calculation
TASK-R04 Create src/hooks/useJobResources.ts — TanStack Query hook fetching GET /api/messebau/jobs/{id}/resources for job-scoped resource display
TASK-R05 Create src/components/fair/FairResourcesTab.tsx — four grids: Workers by Role table (role, needed qty, assigned count, gap, assigned workers list with job labels, "Add Demand" / "Assign Worker" buttons); Trucks table (vehicle name, state badge, assigned to job/dates); Equipment table (item name, state badge, assigned to job/dates); Accommodation table (name, type badge, occupancy, assigned workers with check-in/out dates, "Assign Worker" button); add-demand modal, add-assignment modals per type
TASK-R06 Create src/components/resources/WorkerPoolView.tsx — worker pool management page (reachable from sidebar): list all workers filterable by role+state, CRUD actions, "Add Worker" form; each worker row shows assigned fairs + travel count
TASK-R07 Create src/components/resources/PoolVehicleView.tsx — truck pool management page, same pattern
TASK-R08 Create src/components/resources/EquipmentView.tsx — equipment pool management page, same pattern
TASK-R08a Create src/components/resources/AccommodationPoolView.tsx — accommodation pool management page, same pattern
TASK-R08b Create src/components/resources/WorkerDetailView.tsx — worker detail page: shows worker info + role + state, travel records (list of fairs with outbound/return info, transport method, booking reference, "Add Travel" button, one-way toggle hides return fields), accommodation assignments, fair assignments
TASK-R09 Update Sidebar — add "Ressourcen" nav item (collapsible sub-items: Worker Roles, Worker Pool, Truck Pool, Equipment Pool, Accommodations)
TASK-R10 Write Playwright test: fair-resources.spec.ts — open fair detail → Resources tab → add demand "3 Baristas" → verify gap=3 → create worker Anna (Barista, internal) → assign Anna → verify assigned=1, gap=2 → assign Anna to a specific job → verify job Resources tab shows Anna
TASK-R11 Write Playwright test: accommodation-travel.spec.ts — create accommodation "Hotel Central" → assign worker Anna (check-in 14.6., check-out 18.6.) → verify occupancy=1 → add travel for Anna: one-way, Berlin→Hannover, Bahn, 14.6. 08:00→10:30 → verify worker detail shows travel + accommodation

Implementation Phase 11 — Cross-Cutting & Polish

  • GOAL-011: Production-quality UX, responsive design, PWA, CI/CD.
Task Description Completed Date
TASK-086 Install sonner for toast notifications; create <Toaster> in App.tsx; add showToast(message, type) utility in uiStore
TASK-086a Create src/components/shared/WorkflowProgress.tsx — horizontal stepper showing all 8 phases with current/active/complete states, phase icon + label; below: "Nächster Schritt: [Phase]" button that triggers workflow transition via API; guard violations shown as tooltip with reason; transition history shown as expandable timeline
TASK-087 Add loading skeletons — <Skeleton> components (shadcn/ui) for: stats cards, job cards grid, detail header, form fields, gallery items; shown during TanStack Query isLoading state
TASK-088 Add error boundaries — <ErrorBoundary> wrapping each major route/view, shows error message + retry button calling queryClient.invalidateQueries()
TASK-089 Responsive design — sidebar collapses to bottom tab bar or hamburger menu on screens < 768px; form grids stack to single column; calendar days reduce height; gallery grid reduces columns
TASK-090 Install vite-plugin-pwa — configure service worker with workbox for static asset caching; add offline indicator component that shows when navigator.onLine is false; register PWA manifest with app name "24HRS Messe-Lotse", yellow+black theme color
TASK-091 Create frontend/Dockerfile — multi-stage: Node.js build stage (npm ci && npm run build), nginx stage serving built assets (if serving separately) OR configure Vite build output to Pimcore's public/build/messebau/ directory (if served by Pimcore)
TASK-092 Create .github/workflows/ci.yml — on push/PR: checkout, setup Node 20, npm ci, npm run lint (ESLint), npm run typecheck (tsc --noEmit), npm run test (Vitest), npm run test:e2e (Playwright with Pimcore container as service), npm run build; on main branch push: build and deploy
TASK-093 Run full manual QA — create job, fill all 8 tabs, upload files, save, reload, verify persistence; test calendar month navigation; test PDF export opens correctly; test duplicate creates independent copy; test delete removes all assets; test on mobile viewport; test with viewer role (cannot edit); test with editor role (can edit but not delete); test with admin role (full access)

Implementation Phase 12 — AI Document Processing Skills

  • GOAL-012: Pluggable AI skills triggered on asset upload to Pimcore DAM that extract, validate, and enrich job data from uploaded documents. All suggestions are non-destructive metadata — the user decides to apply or ignore. Skills are field-aware: each upload field maps to a specific skill subset via a routing matrix (see plan/ai-document-skills.md § "Field → Skill Routing Matrix"). The common chain (Classifier → MismatchDetector → VersionDetector) runs on every upload; the field-specific skill chain runs only for fields with applicable skills.

Architecture: asset.postAdd → DocumentClassifier (identifies document type) → type-specific extractor chain → SuggestionService (stores as JSON on job) → frontend renders inline suggestions.

Phase 12a — Infrastructure & Tier 1 (High Impact)

Task Description Completed Date
TASK-AI01 Create SuggestionService in bundles/MessebauBundle/Service/ — stores AI suggestions as a JSON field aiSuggestions on Job DO (array of {id, skill, field, suggestedValue, confidence, sourceAssetId, status(pending/applied/ignored), createdAt}); provides methods to add, apply (writes to actual field), ignore; API endpoint `POST /api/messebau/jobs/{id}/suggestions/{suggestionId}/apply ignore`
TASK-AI02 Create src/components/shared/SuggestionBadge.tsx — yellow notification banner shown inline near affected form field; displays suggested value with confidence percentage, source document name, and [Apply]/[Ignore] buttons; appears alongside existing form fields in Tab 0/1/3/4
TASK-AI03 Create DocumentClassifier — Symfony service triggered on asset.postAdd; analyzes file extension, content type, and filename patterns to classify: briefing, brandbook, floorplan, power-confirmation, rigging-quote, cleaning-confirmation, stand-photo, acceptance-photo, weekly-protocol, consultation-summary, other; routes to correct extraction chain
TASK-AI03a Create DocumentScopeDetector skill — runs during classification (Step 3); determines whether a document is customer-scoped (mentions multiple fairs, brandbook), fair-scoped (mentions "alle Stände", booth counts, hall numbers), or job-scoped (single booth + one company); outputs suggestedScope + suggestedEntityId; triggers relocation suggestion if scope mismatches upload field; example: CFC briefing (112 booths) uploaded to job → suggests move to fair.constructionBriefing
TASK-AI03b Refactor skill chain to be scope-aware — all skills accept a scope parameter (customer|fair|job) and an affectedEntityId; skills output scope + affectedEntityIds[] in their result; SuggestionService stores scope level on each suggestion; frontend tags suggestions with scope badge; propagation: fair-scoped suggestion shows "Apply to all X jobs?"; customer-scoped suggestion shows "Apply to all jobs for this customer?"
TASK-AI04 Create ContactExtractor skill — parses briefing docs (PDF, DOCX) via LLM/text extraction for name/email/phone patterns; returns ISO format contacts; compares with customer contact fields; suggests updates if different
TASK-AI05 Create DeadlineDetector skill (enhanced for German) — scans any document for date patterns including German ranges ("01.–02.07.2026", "Aufbau: DD.–DD.MM.YYYY", "Abbau ab DD.MM. HH:MM Uhr"); maps recognized dates to setup/event/teardown fields using context keywords; compares with stored values; flags mismatches
TASK-AI05a Create MultiFairDetector skill — parses consultation summaries listing multiple fairs; extracts fair name, booth size, stand type, location hints, budgets; suggests bulk creation of Fair records + Job placeholders for the referenced customer; deduplicates against existing fairs
TASK-AI05b Create BudgetExtractor skill — detects currency amounts (€, EUR, k€) with context words (Standbau, Aktivierung, Möbel, Transport); maps to new Job.costEstimate field collection; calculates expected totals for multi-booth briefings
TASK-AI05c Create PainPointSummarizer skill — extracts structured pain points and expectations from consultation/briefing docs; generates a concise structured notes summary; suggests appending to job.notes with [Preview/Append/Replace] options
TASK-AI06 Create ChecklistGenerator skill — extracts requirements/action items from customer briefing text (LLM); generates checklist item suggestions with confidence scores; suggests auto-creation on Tab 4
TASK-AI07 Write PHPUnit tests: DocumentClassifierTest (correct classification per document type), ContactExtractorTest (name/email/phone extraction from sample PDFs), DeadlineDetectorTest (German date ranges, field mapping), MultiFairDetectorTest (multi-fair extraction + dedup), BudgetExtractorTest (currency parsing, context mapping), PainPointSummarizerTest (structured output), ChecklistGeneratorTest (requirement extraction)
TASK-AI08 Write Playwright test: ai-suggestions-tier1.spec.ts — upload Brevo consultation PDF → verify 5 fairs suggested for creation → verify contact Lisa Reinhardt extracted → verify budget amounts detected → verify pain points summarized → apply to notes → verify notes populated; upload CFC briefing → verify German dates parsed (01.–02.07.2026) → verify material checklist items generated

Phase 12b — Tier 2 (Validation)

Task Description Completed Date
TASK-AI09 Create StandDataExtractor skill — parses DWG/DXF/PDF floor plans for dimensions, stand type designators, hall/stand numbers; compares with Tab 0 Stammdaten values; flags discrepancies
TASK-AI10 Create ConfirmationParser skill — parses booking confirmations (power/WLAN/cleaning/rigging PDFs) for service details, quantities, confirmation numbers, dates; compares with booked fields in Organisation tab; sets "confirmed" flag if match; warns if mismatch
TASK-AI11 Create BrandConsistencyChecker skill — extracts brand colors and logo specifications from brandbook (AI/PDF); when stand photos are uploaded, analyzes images for color proximity to brand palette, logo visibility; flags deviations
TASK-AI12 Create DocumentMismatchDetector skill — triggers on DocumentClassifier mismatch; e.g. user uploads floor plan into "Briefing Kunde" area → suggests moving to "Standplan" section; provides one-click reassign to correct asset field
TASK-AI12a Create DocumentVersionDetector skill — when a new asset is uploaded to a field with an existing asset, compares filenames (Levenshtein) + content (dimensions, budgets, dates extraction); flags changes between versions with severity (info/warning/critical); example: OMR 2026 dimensions changed from 8.65×5.8m to 7.50×6.70m (same area, different layout)
TASK-AI13 Write PHPUnit tests: StandDataExtractorTest, ConfirmationParserTest, BrandConsistencyCheckerTest, DocumentMismatchDetectorTest, DocumentVersionDetectorTest
TASK-AI14 Write Playwright test: ai-suggestions-tier2.spec.ts — upload floor plan → verify dimension mismatch warning on Tab 0; upload power confirmation → verify "confirmed" badge + kW mismatch flag; upload brandbook + stand photo → verify color consistency check; upload newer version of OMR briefing over existing one → verify version change detected → compare dimensions → verify warning shown

Phase 12c — Tier 3 (Enhancement)

Task Description Completed Date
TASK-AI15 Create MaterialListExtractor skill — parses briefing text and floor plan legends for material names, furniture items, equipment references; suggests auto-population of Tab 3 Produktion material sections
TASK-AI15a Create LeadMagnetIdeator skill — when briefing requests activation/lead magnet ideas, generates creative suggestions based on company description, product, budget, event type, and previous activations from the document; outputs 3 ideas with title, description, relevance score, budget estimate, and difficulty; displayed as suggestion cards on Tab 3 Leadmagneten section
TASK-AI16 Create AcceptanceReportGenerator skill — combines acceptance photos, signature, remarks from Tab 6; generates structured acceptance PDF with annotated photos, issue list, and sign-off block via Gotenberg
TASK-AI17 Create SetupProgressAnalyzer skill — when stand photos are uploaded at different timestamps, compares images for structural changes (walls up, graphics mounted, furniture placed); auto-checks corresponding checklist items
TASK-AI18 Create WeeklyProtocolSummarizer skill — extracts status updates, open items, decisions from uploaded protocol PDFs; generates a structured summary JSON; renders as a timeline card on the Weekly tab
TASK-AI19 Write PHPUnit tests: MaterialListExtractorTest, LeadMagnetIdeatorTest, AcceptanceReportGeneratorTest, SetupProgressAnalyzerTest, WeeklyProtocolSummarizerTest
TASK-AI20 Write Playwright test: ai-suggestions-tier3.spec.ts — upload briefing → verify material suggestions on Tab 3; upload Brevo OMR briefing with activation request → verify 3 lead magnet ideas generated → select one → verify added to lead magnets; upload two stand photos 2 days apart → verify checklist item auto-checked; upload acceptance photos + remarks → export acceptance report → verify PDF content

3. Alternatives

  • ALT-001: Incremental refactor of existing index.html — Rejected because the current file is 6,200 lines of mixed HTML/CSS/JS with no framework, var usage, localStorage for files, and inline event handlers. Incremental refactoring would be slower and produce lower-quality results than a fresh start with proper tooling.
  • ALT-002: Vue or Svelte instead of React — React chosen for largest ecosystem, shadcn/ui component library support, and broader developer availability. No technical reason against Vue/Svelte; either would work equally well.
  • ALT-003: Separate backend (NestJS/FastAPI) instead of Pimcore — Pimcore is already in the stack and provides DAM, data modeling, auth, and workflow capabilities out of the box. A separate backend would duplicate auth and file management that Pimcore already handles.
  • ALT-004: Client-side PDF generation (jsPDF/html2canvas) — Rejected in favor of Gotenberg (already running) for more reliable rendering, smaller client bundle, and consistency across browsers.

4. Dependencies

ID Dependency Status Notes
DEP-001 Pimcore ≥ 11.x with Docker Compose In place Already running
DEP-002 Gotenberg container (port 3000) In place PDF engine, already configured
DEP-003 OAuth2 configured on Pimcore In place Password grant flow
DEP-004 Node.js ≥ 20 and npm ≥ 9 Required For frontend development and build
DEP-005 Symfony Messenger + cron Pimcore default For async email notifications
DEP-005a symfony/workflow 6.x Pimcore default Symfony Workflow component (bundled with Pimcore, just needs config)
DEP-006 MySQL (Pimcore's database) In place No changes needed
DEP-007 react 18.x, react-dom 18.x Package Core UI framework
DEP-008 @tanstack/react-query 5.x Package Server state management
DEP-009 zustand 4.x Package Client state management
DEP-010 react-hook-form 7.x + zod 3.x Package Form handling + validation
DEP-011 shadcn/ui latest Package Component library (Tailwind-based)
DEP-012 react-router-dom 6.x Package Client-side routing
DEP-013 react-signature-canvas 1.x Package Signature pad for acceptance tab
DEP-014 sonner 1.x Package Toast notifications
DEP-015 vite-plugin-pwa Package PWA service worker generation
DEP-016 axios 1.x Package HTTP client with interceptors
DEP-017 LLM API (OpenAI / Anthropic / self-hosted) External AI extraction requiring NLU; gated behind config — skills use structured regex/parsing as fallback when LLM is unavailable
DEP-018 smalot/pdfparser or spatie/pdf-to-text (PHP) Package PDF text extraction for AI skills; DWG/DXF parsing for floor plans

5. Files

ID File Type Purpose
FILE-001 bundles/MessebauBundle/MessebauBundle.php New Symfony bundle registration
FILE-002 bundles/MessebauBundle/Resources/config/services.yaml New Service definitions
FILE-003 bundles/MessebauBundle/Resources/config/routes.yaml New API route definitions
FILE-004 bundles/MessebauBundle/Controller/Api/FairLocationController.php New FairLocation CRUD endpoints
FILE-005 bundles/MessebauBundle/Controller/Api/CustomerController.php New Customer CRUD endpoints
FILE-006 bundles/MessebauBundle/Controller/Api/FairController.php New Fair CRUD + list jobs for fair + bulk PDF
FILE-007 bundles/MessebauBundle/Controller/Api/JobController.php New Job CRUD endpoints
FILE-008 bundles/MessebauBundle/Controller/Api/AssetController.php New File upload/delete endpoints
FILE-009 bundles/MessebauBundle/Controller/Api/CalendarController.php New Calendar aggregation with effective dates
FILE-010 bundles/MessebauBundle/Controller/MessebauController.php New Serves React SPA index.html
FILE-011 bundles/MessebauBundle/Service/JobDuplicateService.php New Deep clone logic
FILE-012 bundles/MessebauBundle/Service/JobExportService.php New Job PDF via Gotenberg
FILE-013 bundles/MessebauBundle/Service/FairExportService.php New Fair bulk PDF (all jobs) via Gotenberg
FILE-014 bundles/MessebauBundle/EventListener/JobNotificationListener.php New Deadline email reminders
FILE-015 bundles/MessebauBundle/Resources/config/workflows/job.yml New Workflow state machine (8 places, transitions, guards)
FILE-016 bundles/MessebauBundle/Controller/Api/WorkflowController.php New Workflow state + transitions endpoint
FILE-017 bundles/MessebauBundle/EventListener/WorkflowTransitionListener.php New Workflow event hooks
FILE-018 bundles/MessebauBundle/Controller/Api/WorkerRoleController.php New WorkerRole CRUD
FILE-019 bundles/MessebauBundle/Controller/Api/WorkerController.php New Worker pool CRUD
FILE-020 bundles/MessebauBundle/Controller/Api/PoolVehicleController.php New PoolVehicle CRUD
FILE-021 bundles/MessebauBundle/Controller/Api/EquipmentController.php New Equipment CRUD
FILE-022 bundles/MessebauBundle/Controller/Api/FairResourceController.php New Fair demand + assignment endpoints
FILE-023 bundles/MessebauBundle/Controller/Api/JobResourceController.php New Job-scoped resource view
FILE-024 bundles/MessebauBundle/Controller/Api/AccommodationController.php New Accommodation CRUD
FILE-025 bundles/MessebauBundle/Controller/Api/FairAccommodationController.php New Fair-level accommodation assignments
FILE-026 bundles/MessebauBundle/Controller/Api/WorkerTravelController.php New Worker travel CRUD
FILE-026a bundles/MessebauBundle/Service/SuggestionService.php New AI suggestion persistence (store/apply/ignore)
FILE-026b bundles/MessebauBundle/Service/AiSkill/DocumentClassifier.php New Document type detection
FILE-026ba bundles/MessebauBundle/Service/AiSkill/DocumentScopeDetector.php New Scope detection (customer/fair/job) + relocation suggestion
FILE-026c bundles/MessebauBundle/Service/AiSkill/ContactExtractor.php New Tier 1: contact info extraction
FILE-026d bundles/MessebauBundle/Service/AiSkill/DeadlineDetector.php New Tier 1: date detection + field mapping
FILE-026e bundles/MessebauBundle/Service/AiSkill/ChecklistGenerator.php New Tier 1: requirement → checklist items
FILE-026f bundles/MessebauBundle/Service/AiSkill/StandDataExtractor.php New Tier 2: dimensions + stand info from floor plans
FILE-026g bundles/MessebauBundle/Service/AiSkill/ConfirmationParser.php New Tier 2: booking confirmation validation
FILE-026h bundles/MessebauBundle/Service/AiSkill/BrandConsistencyChecker.php New Tier 2: brandbook vs photos comparison
FILE-026i bundles/MessebauBundle/Service/AiSkill/DocumentMismatchDetector.php New Tier 2: wrong-section upload detection
FILE-026j bundles/MessebauBundle/Service/AiSkill/MaterialListExtractor.php New Tier 3: material/furniture from briefing
FILE-026k bundles/MessebauBundle/Service/AiSkill/AcceptanceReportGenerator.php New Tier 3: structured acceptance PDF via Gotenberg
FILE-026l bundles/MessebauBundle/Service/AiSkill/SetupProgressAnalyzer.php New Tier 3: photo timeline → checklist auto-check
FILE-026m bundles/MessebauBundle/Service/AiSkill/WeeklyProtocolSummarizer.php New Tier 3: protocol → structured summary
FILE-026n bundles/MessebauBundle/EventListener/AiSkillTriggerListener.php New Event listener on asset.postAdd → classifier → extraction chain
FILE-026o bundles/MessebauBundle/Service/AiSkill/MultiFairDetector.php New Tier 1: multiple fair detection + bulk creation
FILE-026p bundles/MessebauBundle/Service/AiSkill/BudgetExtractor.php New Tier 1: currency/context extraction → costEstimate
FILE-026q bundles/MessebauBundle/Service/AiSkill/PainPointSummarizer.php New Tier 1: pain points → structured notes
FILE-026r bundles/MessebauBundle/Service/AiSkill/DocumentVersionDetector.php New Tier 2: version diffing (Levenshtein + content comparison)
FILE-026s bundles/MessebauBundle/Service/AiSkill/LeadMagnetIdeator.php New Tier 3: creative activation idea generation
FILE-026t frontend/src/components/shared/SuggestionBadge.tsx New Inline AI suggestion display with Apply/Ignore
FILE-011 templates/messebau/pdf/export.html.twig New PDF export HTML template
FILE-012 templates/messebau/app.html.twig New React SPA shell template
FILE-013 frontend/package.json New Frontend dependencies
FILE-014 frontend/vite.config.ts New Vite build configuration
FILE-015 frontend/tsconfig.json New TypeScript configuration
FILE-016 frontend/tailwind.config.ts New Tailwind CSS configuration
FILE-017 frontend/src/main.tsx New React entry point
FILE-018 frontend/src/App.tsx New Router + providers
FILE-019 frontend/src/api/client.ts New Axios + OAuth2 interceptor
FILE-020 frontend/src/api/fairLocations.ts New FairLocation API functions
FILE-020a frontend/src/api/customers.ts New Customer API functions
FILE-020b frontend/src/api/fairs.ts New Fair API functions
FILE-020c frontend/src/api/jobs.ts New Job API functions
FILE-020d frontend/src/api/calendar.ts New Calendar API functions
FILE-022 frontend/src/stores/authStore.ts New OAuth2 token + user state
FILE-023 frontend/src/stores/uiStore.ts New UI state (views, toasts)
FILE-025 frontend/src/hooks/useFairs.ts New Fair list query hook
FILE-025a frontend/src/hooks/useJobs.ts New Job list query hook (filterable by fairId/customerId)
FILE-025b frontend/src/hooks/useJob.ts New Single job query + mutations
FILE-026 frontend/src/hooks/useCalendar.ts New Calendar data hook
FILE-027 frontend/src/types/job.ts New TypeScript interfaces
FILE-028 frontend/src/components/layout/AppShell.tsx New Main layout wrapper
FILE-028a frontend/src/hooks/usePermissions.ts New Role-based permission flags (canEdit, canDelete, canCreateJob, canTransitionWorkflow, canManageResources, canSeeOrgTab, canSeeProduktionTab, canSeeWeeklyTab, canAddChecklistItems, isCustomer)
FILE-029 frontend/src/components/layout/Sidebar.tsx New Navigation sidebar
FILE-030 frontend/src/components/auth/LoginPage.tsx New OAuth2 login form
FILE-031 frontend/src/components/auth/ProtectedRoute.tsx New Auth guard component
FILE-035 frontend/src/components/dashboard/FairCard.tsx New Fair card in dashboard grid
FILE-035a frontend/src/components/dashboard/FairCardGrid.tsx New Fair cards layout
FILE-035b frontend/src/components/dashboard/SearchBar.tsx New Fair/job search input
FILE-035c frontend/src/components/dashboard/EmptyState.tsx New Empty state view
FILE-035d frontend/src/components/fair/FairDetailView.tsx New Fair detail "todo" view (all jobs for a fair)
FILE-035e frontend/src/components/job/CreateJobFlow.tsx New Multi-step job creation wizard
FILE-035f frontend/src/components/job/JobCard.tsx New Individual job card
FILE-035g frontend/src/components/job/JobCardGrid.tsx New Job cards layout
FILE-035h frontend/src/components/fair/FairResourcesTab.tsx New Fair-level resource demand + assignment grids
FILE-035i frontend/src/components/resources/WorkerPoolView.tsx New Worker pool management page
FILE-035j frontend/src/components/resources/PoolVehicleView.tsx New Truck pool management page
FILE-035k frontend/src/components/resources/EquipmentView.tsx New Equipment pool management page
FILE-035l frontend/src/api/workerroles.ts New WorkerRole API functions
FILE-035m frontend/src/api/workers.ts New Worker API functions
FILE-035n frontend/src/api/poolvehicles.ts New PoolVehicle API functions
FILE-035o frontend/src/api/equipment.ts New Equipment API functions
FILE-035p frontend/src/api/fairResources.ts New Fair resource demand + assignment API
FILE-035q frontend/src/types/resources.ts New Resource TypeScript interfaces
FILE-035r frontend/src/hooks/useFairResources.ts New Fair resources query hook
FILE-035s frontend/src/hooks/useJobResources.ts New Job resources query hook
FILE-035t frontend/src/components/resources/AccommodationPoolView.tsx New Accommodation pool management page
FILE-035u frontend/src/components/resources/WorkerDetailView.tsx New Worker detail page (travel + accommodation + assignments)
FILE-035v frontend/src/api/accommodations.ts New Accommodation API functions
FILE-035w frontend/src/api/travel.ts New Travel API functions
FILE-036 frontend/src/components/job-detail/DetailHeader.tsx New Job detail toolbar with breadcrumb
FILE-038 frontend/src/components/job-detail/TabContainer.tsx New Tab navigation wrapper
FILE-039 frontend/src/components/job-detail/tabs/MasterDataTab.tsx New Tab 0: Stammdaten
FILE-040 frontend/src/components/job-detail/tabs/BriefingTab.tsx New Tab 1: Briefing
FILE-041 frontend/src/components/job-detail/tabs/OrganisationTab.tsx New Tab 2: Organisation
FILE-042 frontend/src/components/job-detail/tabs/ProduktionTab.tsx New Tab 3: Produktion
FILE-043 frontend/src/components/job-detail/tabs/DokumenteTab.tsx New Tab 4: Dokumente
FILE-044 frontend/src/components/job-detail/tabs/StandfotosTab.tsx New Tab 5: Standfotos
FILE-045 frontend/src/components/job-detail/tabs/StandabnahmeTab.tsx New Tab 6: Standabnahme
FILE-046 frontend/src/components/job-detail/tabs/WeeklyTab.tsx New Tab 7: Weekly
FILE-047 frontend/src/components/calendar/CalendarGrid.tsx New Calendar month grid
FILE-048 frontend/src/components/calendar/CalendarDay.tsx New Single calendar day
FILE-049 frontend/src/components/calendar/CalendarToolbar.tsx New Month navigation
FILE-050 frontend/src/components/shared/Lightbox.tsx New Image lightbox overlay
FILE-050a frontend/src/components/shared/WorkflowProgress.tsx New Workflow phase stepper + transition buttons
FILE-051 frontend/Dockerfile New Production frontend build
FILE-052 .github/workflows/ci.yml New CI/CD pipeline
FILE-053 frontend/tests/ New Vitest + Playwright tests
FILE-054 index.html Archive No longer needed after deploy

6. Testing

ID Test Type Description
TEST-001 FairLocationControllerTest PHPUnit CRUD with auth/roles
TEST-002 CustomerControllerTest PHPUnit CRUD with auth/roles
TEST-003 FairControllerTest PHPUnit CRUD + /fairs/{id}/jobs listing + bulk PDF
TEST-004 JobControllerTest PHPUnit CRUD with filters (fairId, customerId, workflowState), effective date resolution
TEST-005 JobDuplicateServiceTest PHPUnit Deep clone with preserved fair/customer relations, copied assets
TEST-006 JobExportServiceTest PHPUnit PDF via Gotenberg, effective date resolution in template
TEST-007 FairExportServiceTest PHPUnit Combined PDF for all jobs in a fair
TEST-008 CalendarControllerTest PHPUnit Event aggregation with effective dates, fairId filter
TEST-009 WorkerRoleControllerTest PHPUnit Role CRUD
TEST-010 WorkerControllerTest PHPUnit Worker CRUD with role+state filters
TEST-011 FairResourceControllerTest PHPUnit Demand CRUD, assignment CRUD, gap computation (needed − assigned), assignment scoping by fair/job
TEST-012 DocumentClassifierTest PHPUnit Correct classification per document type (briefing/brandbook/floorplan/confirmation/photo/protocol)
TEST-013 ContactExtractorTest PHPUnit Name/email/phone extraction from sample PDFs, ISO formatting, comparison with stored values
TEST-014 DeadlineDetectorTest PHPUnit Date parsing (multiple formats), context-based field mapping, mismatch flagging
TEST-015 ChecklistGeneratorTest PHPUnit Requirement extraction from text, checklist item generation with confidence scores
TEST-016 StandDataExtractorTest PHPUnit Dimension + stand type extraction from floor plan samples
TEST-017 ConfirmationParserTest PHPUnit Service detail extraction from booking confirmation PDFs
TEST-018 SuggestionServiceTest PHPUnit Store/apply/ignore flow, apply writes to actual DO field, ignore removes from pending list
TEST-004a WorkflowTest PHPUnit Assert initial state is angebot; test each transition: valid (applied), blocked-by-guard (rejected with reason); test date-based auto-transitions; test abgeschlossen applies read-only flag
TEST-004b WorkflowTransitionListenerTest PHPUnit Assert audit log written on every transition; assert PDF generation triggered on aufbau enter; assert email dispatched on messe enter
TEST-005 authStore.test.ts Vitest Login flow, token refresh, logout, sessionStorage persistence
TEST-006 client.test.ts Vitest Token attachment, 401 refresh + retry, concurrent refresh queue
TEST-007 useJobs.test.ts Vitest Query caching, search debounce, pagination
TEST-012 create-fair-and-jobs.spec.ts Playwright Create fairLocation → create customer → create fair at location → create two jobs for different customers at same fair → verify both appear in fair detail → verify fair card on dashboard shows correct job count
TEST-013 edit-job.spec.ts Playwright Open job → edit fields across tabs → override dates → save → reload → verify overrides persisted, inherited dates shown when override cleared
TEST-014 upload-files.spec.ts Playwright Upload images and PDFs to various tabs, verify previews, download, delete
TEST-015 duplicate-delete.spec.ts Playwright Duplicate job → verify "(Kopie)" + same customer/fair → delete original → verify gone from fair detail
TEST-016 calendar-navigation.spec.ts Playwright Navigate months, filter by fair, click event → navigate to job detail
TEST-017 pdf-export.spec.ts Playwright Export single job PDF + bulk fair PDF → verify downloads
TEST-018 roles-access.spec.ts Playwright Viewer (cannot edit/delete, read-only), editor (can edit, cannot delete), admin (full); customer: login, verify only own jobs visible, verify 4/8 tabs shown, verify cannot see org/produktion/weekly tabs, verify cannot create/delete/duplicate, verify can check/uncheck checklist items but not add/remove, verify can export own job PDF
TEST-019 workflow-progression.spec.ts Playwright Create job (verify "Angebot"), assign customer+fair → transition "Planung", upload briefing → transition "Organisation", verify guard blocks skip
TEST-020 responsive.spec.ts Playwright 375px viewport: sidebar collapses, forms stack, gallery adjusts
TEST-021 offline.spec.ts Playwright Go offline, verify indicator, verify cached static assets load
TEST-022 fair-resources.spec.ts Playwright Open fair → Resources tab → add demand "3 Baristas" → verify gap=3 → create worker Anna (Barista, internal) → assign Anna to fair → verify assigned=1, gap=2 → assign Anna to a specific job → verify job Resources tab shows Anna
TEST-023 accommodation-travel.spec.ts Playwright Create accommodation → assign worker → verify occupancy → add one-way travel → verify worker detail shows travel
TEST-024 ai-suggestions-tier1.spec.ts Playwright Upload briefing PDF → verify contact suggestion appears → apply → verify field populated; verify deadline suggestion with mismatch warning → ignore → dismissed; verify checklist suggestion → apply → items created on Tab 4
TEST-025 ai-suggestions-tier2.spec.ts Playwright Upload floor plan → verify dimension mismatch warning on Tab 0; upload confirmation → verify "confirmed" badge + kW mismatch flag; upload brandbook + stand photo → verify color consistency check
TEST-026 ai-suggestions-tier3.spec.ts Playwright Upload briefing → verify material suggestions on Tab 3; upload 2 stand photos 2 days apart → verify checklist auto-checked; upload acceptance photos → export acceptance report → verify PDF content

7. Risks & Assumptions

ID Description
RISK-001 Pimcore Data Object serialization performance — deeply nested field collections + relation objects may cause slow API responses. Mitigation: lazy-load relations, use Pimcore's getBy with force parameter selectively, paginate related items, consider denormalizing some fields into MessebauJob directly if performance degrades.
RISK-002 File upload timeout — large files uploaded over slow connections may exceed PHP max_execution_time or proxy timeout. Mitigation: set reasonable file size limits (10 MB), implement chunked upload for future if needed.
RISK-003 OAuth2 token refresh race condition — multiple concurrent API calls on 401 could trigger multiple refresh attempts. Mitigation: Axios interceptor with a refresh promise queue — only one refresh fires, all other 401s wait for its result.
RISK-004 Booking platform password stored in plaintext — the current app stores the booking platform password in localStorage. Mitigation: use Pimcore's encrypted field type for the password field, or at minimum add a prominent notice in the UI.
RISK-005 Gotenberg availability — PDF export depends on Gotenberg being reachable. Mitigation: implement health check on the export endpoint, return clear error if Gotenberg is down, consider client-side print fallback.
RISK-006 Workflow guard fragility — guard logic embedded in YAML expressions may become complex and hard to test. Mitigation: keep guards simple (field-not-null checks), defer complex validation to custom guard classes (GuardInterface) for the organisation→produktion and produktion→aufbau transitions.
RISK-007 N+1 query problem with 4 entities — serializing a Job with joined Customer + Fair + FairLocation could trigger excessive queries on list endpoints. Mitigation: use Pimcore's getBy API with eager-loading, flatten frequently-needed fields into the Job response directly (e.g. customerName, fairName, fairLocationName as denormalized cache), add response caching headers.
RISK-008 Resource gap calculation performance — computing count(WorkerAssignment) grouped by role for a fair with 100+ jobs could be slow. Mitigation: denormalize assignedCount and gap into the API response in the controller (single aggregation query per role), not computed client-side.
RISK-009 AI hallucination in document extraction — LLM-based extraction may produce plausible but incorrect data. Mitigation: all AI output is non-destructive suggestions only; user must explicitly Apply; confidence scores shown; structured regex/parsing used as fallback when LLM confidence is below threshold; Phase 1 skills use minimal LLM (structured extraction preferred).
RISK-010 LLM cost at scale — per-asset LLM calls could become expensive with high document volume. Mitigation: skills triggered only on asset.postAdd (once per upload, not re-analyzed); results cached on the suggestion record; configurable LLM provider (self-hosted option); Tiers 2-3 are deployed only after Tier 1 validates the ROI.
ASSUMPTION-001 Pimcore's REST API / Data Hub is suitable for the frontend's needs. If response times are too slow, a thin caching layer (Symfony cache or API Gateway) can be added.
ASSUMPTION-002 The existing Docker Compose setup has sufficient resources to add the frontend build container.
ASSUMPTION-003 Team is comfortable with React + TypeScript. If not, Vue or Svelte can be substituted with minimal plan changes.
ASSUMPTION-004 No regulatory requirements (GDPR article 32, etc.) mandate encryption at rest beyond what Pimcore provides. If so, encrypted field type must be used for all contact/personal data.