Design & uitvoerplan

Resterend werk & rapportage fase 3

Van inventarisatie naar plan. Al het werk dat nog open staat ten opzichte van het oorspronkelijke design, in volgorde gezet — met de maandrapportage als grootste brok volledig uitontworpen.

4 brokken in volgorde Geverifieerd tegen code & git Provider-agnostisch ontworpen
01

Roadmap

Eén overkoepelende route die elke resterende brok in volgorde zet, met per brok de aanpak, afhankelijkheden en beslismomenten. Elke grote brok krijgt later zijn eigen spec.

Brok 1 · geen apart design · klein

Quick-win-batch

Klein, geïsoleerd, geen schema-impact. Samen uitvoerbaar in één PR.

  1. Dubbele opgeleverd-datum weg. Bij task.doneAt rendert nu zowel het "Voortgang"-blok in de hoofdkolom als de "Opgeleverd"-kaart in de rail dezelfde datum. Het hoofdkolom-blok vervalt; de rail-kaart blijft. De echte status-tijdlijn op die plek komt in Brok 3.
  2. Assignee als initiaal-badge. Toegewezen personen tonen nu als kommalijst. Vervangen door initiaal-badges, consistent met de thread-avatars.
  3. Geel-token afronden. --brand-yellow exact naar #F9C10F (officieel merkgeel) voor het logo; UI-highlight #f9bf2c blijft ongemoeid.
Exit-criterium: klant-detailpagina toont de opgeleverd-datum één keer, assignees als badges, en het logo gebruikt het officiële merkgeel. Bestaande tests groen.
Brok 2 · voorwaarde voor Brok 3 · middel

Eerste deploy / productie-omgeving

Er is nu geen productie-omgeving (alleen ci.yml, geen deploy-config). De rapportage-sync-job moet ergens dagelijks draaien, dus dit gaat eraan vooraf.

  • Provider-agnostisch ontworpen. App, Postgres en een beveiligde trigger-route worden zo opgezet dat de host-keuze los staat van de code.
  • Definitieve keuze (Laravel Forge + DigitalOcean Droplet vs DO App Platform) valt bij uitvoering — zie de host-afweging.
  • Omvat: deploy-pipeline, productie-TOKEN_ENCRYPTION_KEY + secrets, migraties in productie, een beveiligde cron-trigger-route.
Exit-criterium: de app draait op een productie-URL met een werkende, beveiligde trigger-route die handmatig en via cron aangeroepen kan worden.
Brok 3 · eigen spec · groot

Fase 3 — Rapportage

De grootste brok en de oorspronkelijke kern-businessreden: de maandrapportage automatiseren. Volledig uitgewerkt verderop op deze pagina.

Bevat: statushistorie-sync-job, monthly_reports-datamodel met uren-accordering en audit-trail, admin-editor, concept → gepubliceerd → un-publish-flow, klant rapportage-overzicht + detail. De status-tijdlijn uit Brok 1 landt hier.

afhankelijk van  Brok 2 (deploy + cron-trigger)

Brok 4 · eigen latere spec · groot

Fase 4 — AI-assistentie

AI-conceptverhaal voor het kwalitatieve deel van de rapportage, met governance en handmatige fallback. Volgt ná Brok 3 (heeft de rapportage-aggregaties als input).

afhankelijk van  Brok 3

02

Bewust geparkeerd

Deze stonden in het oorspronkelijke design of de fase-1-spec, maar zijn bewust buiten de MVP. Hier expliciet vastgelegd zodat het geen "vergeten gaten" meer zijn.

ItemRedenBron
Algemene thread (taak-onafhankelijk)YAGNI; per-taak reacties dekken de behoefte. Heroverwegen op klant-feedback.fase-1-spec, fase 2
Notificatie-centrum (frame 16)YAGNI; "Wacht op jou"-KPI dekt de behoefteschermen-audit
E-mailnotificatie bij klant-reactieTeam checkt het portaal; uitbreidbaar laterklant-reacties-spec
PDF-export rapportageLatere toevoegingfase-1-spec
Meerdere accounts per klant (frame 17)Buiten MVPfase-1-spec
Slack, feature-toggles, lange-termijn-analyticsBuiten MVPfase-1-spec
03

Host-afweging

Provider-agnostisch ontworpen; de definitieve keuze valt bij de eerste deploy (Brok 2). Vastgelegde opties met hun trade-offs.

AspectForge + DO DropletDO App Platform
ServerbeheerJij / Forge (OS, patches, Postgres)DO regelt het
Postgreslokaal, $0 extraaparte Managed DB (~+$15/mnd)
Cron / sync-jobnative, geen timeoutscheduled job, timeout-grens
Long-running syncgeen probleemkan knellen
Kosten~$24–32/mnd, vast~$20–27/mnd
Forge-workflowja (voorkeur)n.v.t.
De sync-job is daarom provider-agnostisch: een gewone server-functie met een beveiligde trigger-route. Welke cron hem aanroept (Forge-scheduler, App-Platform-job, extern) is een deploy-detail, geen ontwerp-beslissing.
04

Rapportage — fase 3 design

De maandelijkse klant-rapportage automatiseren: Congos stelt per klant per maand een rapportage op (kwantitatief uit ClickUp-historie, uren als geaccordeerd bevroren cijfer, kwalitatief verhaal), publiceert die, en de klant ziet hem als pagina in het dashboard.

AI-assistentie voor het kwalitatieve deel valt buiten deze spec (fase 4). Deze spec levert de volledige handmatige flow; de kwalitatieve tekst schrijft Congos hier zelf.

Architectuur op hoofdlijnen

Drie onafhankelijk testbare onderdelen, elk met een eigen datamodel-grens:

AStatushistorie-syncDagelijkse job. ClickUp → snapshot in task_status_history. Idempotent per dag.
BRapportage opstellenAdmin. Historie → aggregatie → concept → muteren → publiceren.
CRapportage tonenKlant. Uitsluitend gepubliceerde rapportages, read-only.

Onderdeel A · Statushistorie-sync

Dagelijkse rij per taak — behoudt ruwe data zodat we later nieuwe metrics kunnen afleiden. De UNIQUE-constraint maakt de job veilig herhaalbaar.

task_status_history
  id            uuid pk
  client_id     uuid   -- fk clients, RLS-scoping + cache-isolatie
  list_id       text   -- welke gekoppelde lijst
  task_id       text   -- ClickUp-taak-id
  snapshot_date date   -- dag van de snapshot (Europe/Amsterdam)
  status        text   -- ruwe ClickUp-status op die dag
  task_name     text   -- momentopname, leesbaarheid
  created_at    timestamptz
  UNIQUE (task_id, snapshot_date)   -- idempotent per dag
  • De job runStatusSnapshot() loopt over alle klanten met gekoppelde lijsten, haalt per lijst de taken op via de bestaande guest-token-flow, en schrijft voor vandaag een rij per taak.
  • Provider-agnostische trigger: route POST /api/cron/status-snapshot, beveiligd met een gedeeld geheim (CRON_SECRET).
  • Fail-safe: per lijst een try/catch — één falende lijst blokkeert de rest niet. Falen wordt gelogd; de job rapporteert hoeveel lijsten slaagden of faalden (geen stille skip).
  • Tijd-in-status & doorlooptijd worden afgeleid door opeenvolgende snapshots te vergelijken, niet opgeslagen. Bij gaten: best-effort met eerlijke kwalificatie.

Onderdeel B · Rapportage opstellen (admin)

monthly_reports
  id              uuid pk
  client_id       uuid
  year            int
  month           int    -- 1-12
  status          text   -- 'concept' | 'gepubliceerd'
  metrics_json    jsonb  -- afgeleid bij opstellen, bevroren bij publiceren
  suggested_hours numeric -- wat ClickUp gaf
  approved_hours  numeric -- wat Congos ervan maakte
  narrative_md    text   -- Congos schrijft (markdown)
  published_at    timestamptz null
  UNIQUE (client_id, year, month)

monthly_report_history   -- audit-trail na publicatie
  report_id  uuid · field text · old_value text · new_value text · changed_at

Flow

  1. Aanmaken concept. Admin kiest klant + maand. Systeem bouwt metrics_json uit de historie en haalt suggested_hours als momentopname uit ClickUp. approved_hours start gelijk. Status = concept.
  2. Editor (frame 5). Kwantitatieve blokken read-only afgeleid; admin muteert de uren en schrijft het kwalitatieve verhaal. Niets zichtbaar voor de klant zolang concept.
  3. Publiceren (frame 6). Bevestiging-modal. metrics_json + approved_hours bevriezen; latere ClickUp-mutaties raken een gepubliceerde rapportage niet meer.
  4. Un-publish. Terug naar concept voor herziening; klant ziet de maand dan tijdelijk niet. Elke wijziging na de eerste publicatie schrijft een audit-trail-rij.
Edge cases: maand zonder activiteit → expliciete "geen activiteit"-staat · incomplete historie → eerlijke kwalificatie ("N van ~30 dagsnapshots") · dubbele aanmaak voorkomen via de unique-constraint.

Onderdeel C · Rapportage tonen (klant)

  • Overzicht (frame 1): lijst van gepubliceerde maanden, server-side gescoped op client_id uit de sessie. Alleen gepubliceerd zichtbaar. Route /rapportages.
  • Detail (frame 2): één maand — KPI-blokken + trend + uren (approved_hours) + het verhaal via de bestaande <Markdown>-pipeline. Route /rapportages/[year]-[month].
  • Uren uitsluitend hier zichtbaar — nergens anders in de klant-view.
  • Trendgrafiek: hergebruik de bestaande Sparkline-component; geen nieuwe zware chart-dependency tenzij echt nodig (YAGNI).

Beveiliging & tenant-isolatie

  • task_status_history en monthly_reports krijgen RLS-policies gescoped op client_id (zelfde patroon als bestaande tabellen). Klant-rol leest alleen eigen gepubliceerde rapportages; schrijven via app_service.
  • De cron-route is geen klant-route: aparte auth via CRON_SECRET, niet via sessie. Aggregatie en publicatie draaien onder requireAdminSession.
  • ClickUp-comments en interne notities komen nooit in de rapportage of historie — alleen status, taaknaam en uren-momentopname.

Testing

OnderdeelWat de test bewijst
SyncIdempotentie (twee keer dezelfde dag = geen dubbele rij), één falende lijst blokkeert de rest niet, juiste velden.
AggregatieDoorlooptijd / tijd-in-status correct uit snapshots; gaten geven eerlijke kwalificatie; maand-zonder-activiteit geeft lege-staat.
Publicerenmetrics_json + approved_hours bevriezen; un-publish verbergt voor de klant; audit-trail per wijziging.
Tenant-isolatieKlant ziet nooit andermans rapportage of een concept (RLS).
Uren-zichtbaarheidUren verschijnen alleen in een gepubliceerde rapportage.

Faseringsvolgorde binnen deze spec

  1. Onderdeel A (sync + datamodel + cron-route) — levert de databron.
  2. Onderdeel B (datamodel + aggregatie + admin-editor + publiceren).
  3. Onderdeel C (klant-overzicht + detail).
  4. Status-tijdlijn op de taak-detailpagina (Brok 1.1, nu er historie is).

Elk onderdeel is apart mergebaar met een eigen exit-criterium.

Klant-dashboard · roadmap & rapportage fase-3 design · 4 juni 2026

Bronnen: openstaand-werk-inventarisatie.md · 2026-06-04-roadmap-design.md · 2026-06-04-rapportage-fase-3-design.md