82 KiB
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
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
customerrecord; 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
jobentity — 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); onefaircontains manyjobs, onecustomercan have multiplejobs at the samefair - REQ-009: Date inheritance —
jobinheritssetupStart/End,eventStart/End,teardownStart/Endfrom its parentfair, with per-joboverride capability - REQ-010: "Fair Detail" view — the primary "todo" grouping: open a
fair→ see all itsjobs across allcustomers, 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 viaWorkerDemand(role + quantity), fill withWorkerAssignment(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,contractDocsfor company-wide documents.fair.constructionBriefing,floorPlan,regulations,protocolsfor fair-wide documents. Job retains booth-specific documents (confirmations, photos, acceptance, signature). AIDocumentScopeDetectoridentifies 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,varusage, 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. |
8. Related Specifications / Further Reading
- Pimcore Data Objects Documentation
- Pimcore REST API / Data Hub
- Pimcore Asset/DAM Documentation
- Symfony Workflow Component
- Gotenberg Chromium Conversion
- TanStack Query Documentation
- shadcn/ui Components
- React Signature Canvas
- Original
index.htmlreference:/srv/dockerBuilds/messeLotse/index.html(6,204 lines)