Site Protocols

Construction-site protocols (Baustellenprotokolle) documented per building: walk-throughs, acceptance protocols, kick-off and construction meetings, defect inspections. Lives as the “Protokolle” tab on the building dashboard (I#1963, PR#1969), with PDF export added in I#1988 / PR#1994.

Data Model

site_protocols (backend/src/db/schema.ts): buildingId (FK → buildings, cascade), type (enum site_protocol_type), title, protocolDate (YYYY-MM-DD), body (JSONB), createdBy / finalizedBy (FK → contacts.id, per Human FK Rule), visibleToParticipants, finalizedAt. Indexed on building, type, date, and both contact FKs.

Protocol types: site_protocol, walk_through, group_appointment, acceptance_protocol, kick_off_meeting, defect_walk_through, construction_meeting, free_form — displayed via the protocolTypes.* keys in the dashboard i18n namespace.

The body JSONB (Zod-validated in the route layer) holds:

  • location, notes — free text
  • attendees[] — name, company, role, optional contactId, signed flag
  • topics[] — title, description, decision
  • openPoints[] — title, responsible, dueDate, status (open/done)
  • photos[]fileId (CAS reference, see Files) + caption

site_protocol_participant_visibility: per-contact portal whitelist (protocolId + contactId, unique pair, cascade both ways).

API

Mounted under /api/buildings, backed by SiteProtocolServiceImpl (backend/src/services/site-protocol-service.ts); building access checked via canAccessBuilding() with a leak-proof 404 surface; all actions audit-logged.

MethodPathPurpose
GET/:buildingId/site-protocolsList protocols for a building
GET/:buildingId/site-protocols/:idSingle protocol + visibility contact IDs
POST / PUT/:buildingId/site-protocols(/:id)Create / update
POST/:buildingId/site-protocols/:id/finalizeLock content (immutable afterwards)
GET/:buildingId/site-protocols/:id/pdfPDF export (attachment download)
DELETE/:buildingId/site-protocols/:idDelete (rejected once finalized)
PUT/:buildingId/site-protocols/:id/visibilitySet per-contact portal visibility
GET/:buildingId/portal-audienceCustomer + partner roster for the visibility picker

PDF Export

renderSiteProtocolPdf() in backend/src/services/site-protocol-pdf.ts renders an A4 document with pdf-lib: RENEWA letterhead, building address, type/date/title block, attendees/topics/open-points tables, embedded photos (capped height, never split across page breaks), legal footer. German default, English supported via a message-map shim (backend i18next not wired yet — same pattern as the participants export). Photos are fetched defensively through FileService.download(); missing blobs become placeholder notes instead of failing the export. The route streams the buffer as an attachment named from the protocol date.

Frontend

Tab protocols registered in DASHBOARD_TABS (frontend/src/pages/dashboard/dashboardService.ts), URL-synced via useTabState(); rendered from BuildingDetail.tsx.

  • ProtocolsSection.tsx (pages/dashboard/construction/) — thumbnail grid, type picker with color-coded icons, empty state, delete confirmation.
  • ProtocolEditor.tsx + useProtocolEditorState() — composed of ProtocolSectionHeader, ProtocolAttendeesSection, ProtocolTopicsSection, ProtocolOpenPointsSection, ProtocolPhotosSection; finalize button, PDF download, portal-visibility dialog.
  • protocolEditorService.ts — pure helpers (type ordering/colors, downloadProtocolPdf(), date formatting), following the standard component decomposition pattern.

Query hooks in frontend/src/lib/queries/siteProtocolQueries.ts: useProtocols, useProtocol, useCreateProtocolMutation, useUpdateProtocolMutation, useFinalizeProtocolMutation, useDeleteProtocolMutation, useSetProtocolVisibilityMutation, usePortalAudience. API layer: frontend/src/lib/api/site-protocols.ts.

Lifecycle and Visibility

Drafts are fully editable; finalizing sets finalizedAt and locks content (updates and deletes rejected) while visibility sharing stays editable. Two visibility mechanisms: the coarse visibleToParticipants flag and the per-contact whitelist feeding the customer/partner Portal. The portal audience aggregates building-wide customers (customer/owner/main-contact roles) and partners (contractor + referrer contacts from quotes), reusing DashboardService.getParticipants() for consistency with the Baubeteiligte tab. No mock data is seeded for site protocols.

See Also