20 KiB
🚀 Migratieplan: /clients/ → /patients/ Route Consolidatie
Datum: 2025-11-22 Versie: 1.0 Status: ✅ Voltooid Gekozen Strategie: Custom API (Optie B)
📋 Executive Summary
Doel: Consolideer beide routes naar één FHIR-compliant /patients/ route met Custom API voor Intakes.
Inschatting: 10-12 story points (2-3 werkdagen) Risico Level: Medium Breaking Changes: Ja (mitigated door redirects)
🔧 Fase 0 — Schema & Runbook
Status: ✅ Repo gealigneerd (feb 2026). Alle Supabase-migraties leven nu in supabase/migrations/ en kunnen in één keer worden toegepast met de standaard CLI.
Benodigde migraties
20241115000001_create_leads_table.sql20241115000002_create_epd_core_tables.sql20241115000004_create_demo_users.sql20241121_migrate_legacy_to_fhir.sql20241121_seed_demo_data.sql20251119094908_auth_hook_duplicate_email.sql20251122_screening_intake_schema.sql20251122_seed_default_organization.sql
Runbook
- Start Supabase lokaal (
supabase start) of log in op de gewenste omgeving. - Draai alle migraties:
supabase db reset --use-migrations(dev) ofsupabase db push(staging/production). Hiermee wordt het schema gelijkgetrokken metsupabase/migrations/. - Seed basisdata:
pnpm ts-node scripts/seed-organization.ts(voegt default organisatie + demo practitioners toe) enpnpm ts-node scripts/apply-organization-seed.shindien nodig. - Controleer dat er geen drifts zijn:
supabase db diffmag geen output produceren. - Smoke-test FHIR + Custom API:
curl -s http://localhost:3000/api/fhir/Patient?_count=1curl -s http://localhost:3000/api/intakes?patientId=<uuid>
- Handmatige datafix (eenmalig): archiveer legacy
/clients/records metUPDATE clients SET archived=trueof verwijder de oude tabellen na validatie. Nieuwe dossiers worden uitsluitend viapatientsbeheerd. - Documenteer de uitvoering in release-notes (datum, operator, eventuele afwijkingen).
Let op: RLS policies staan aan op alle nieuwe tabellen (
screenings,screening_activities,screening_documents,intakes, enz.). Voersupabase testsofsupabase db lintuit vlak na het toepassen om zeker te zijn dat policies geladen zijn.
🎯 Probleem Statement
Momenteel bestaan er twee parallelle implementaties voor patiënt/cliënt beheer:
-
/app/epd/clients/- Oudere implementatie (17-19 nov)- Directe Supabase queries
- Bevat werkende Intake module (Epic 4)
- Niet FHIR-compliant
-
/app/epd/patients/- Nieuwere implementatie (21-22 nov)- FHIR API compliant
- Betere features (BSN validatie, filters, paginatie)
- Intake nog placeholder
Impact:
- Verwarring over canonical route
- Code duplicatie
- Inconsistente architectuur
- Moeilijke maintenance
📊 Huidige Situatie Analyse
/clients/ Route Inventory
✅ Volledig Geïmplementeerd:
| Component | LOC | Functionaliteit |
|---|---|---|
intakes/components/intake-list.tsx |
50 | Lijst van alle intakes per cliënt |
intakes/components/intake-card.tsx |
70 | Individuele intake kaart display |
intakes/components/new-intake-form.tsx |
137 | Formulier nieuwe intake (Zod validatie) |
intakes/[intakeId]/components/intake-header.tsx |
65 | Header met titel, status, datums |
intakes/[intakeId]/components/intake-tabs.tsx |
52 | Tab navigatie binnen intake |
intakes/[intakeId]/page.tsx |
52 | Intake detail algemene informatie |
intakes/[intakeId]/layout.tsx |
29 | Layout wrapper voor intake detail |
intakes/actions.ts |
83 | Server actions (CRUD operations) |
Total: ~538 LOC werkende functionaliteit
Server Actions (Supabase-based):
export async function getIntakesByClientId(clientId: string)
export async function createIntake(input: CreateIntakeInput)
export async function getIntakeById(intakeId: string)
⏳ Placeholders (geen migratie nodig):
- Profile Tab ("Coming Soon Week 3")
- Plan Tab ("Coming Soon Week 3")
- Reports pagina
- Diagnose pagina
📦 Basis Functionaliteit (inferieur aan /patients/):
- Client list (geen status/gender filters, geen paginatie)
- Client form (geen BSN validatie, geen John Doe)
- Dashboard (basic, geen unique features)
/patients/ Route Inventory
✅ Al Geïmplementeerd (Superieur):
- Patient list met advanced filtering
- Patient form met BSN 11-proef validatie
- John Doe support
- Delete functionaliteit (two-step confirmation)
- Modern layout (ClientHeader + ClientSidebar)
- Dashboard met quick actions
❌ Ontbreekt (te migreren van /clients/):
- Intake module (volledig)
🎯 Gekozen Architectuur: Custom API (Optie B)
Waarom Custom API?
✅ Voordelen:
- Snellere implementatie (1-2 dagen vs 3-4 dagen)
- Eenvoudiger data model (direct mapping naar
intakestabel) - Minder transformatie logica nodig
- Bestaande database schema hergebruiken
- Type safety met TypeScript
⚠️ Trade-offs:
- Niet FHIR Encounter-compliant (maar acceptabel voor MVP)
- Toekomstige refactor naar FHIR mogelijk nodig
- Aparte API naast FHIR Patient/Practitioner
🔮 Toekomst Path:
- Behoud Custom API voor Intakes in MVP
- Plan FHIR Encounter mapping in latere fase (Epic 5/6)
- Incrementele migratie mogelijk zonder breaking changes
API Specificatie
Endpoint: /api/intakes
Routes:
GET /api/intakes?patientId={id} // List intakes for patient
POST /api/intakes // Create new intake
GET /api/intakes/{intakeId} // Get intake by ID
PUT /api/intakes/{intakeId} // Update intake
DELETE /api/intakes/{intakeId} // Delete intake
Request/Response Types:
interface Intake {
id: string;
patient_id: string;
title: string;
department: 'Volwassenen' | 'Jeugd' | 'Ouderen';
status: 'Open' | 'Afgerond';
start_date: string;
end_date?: string;
notes?: string;
psychologist_id?: string;
created_at: string;
updated_at: string;
}
interface CreateIntakeInput {
patient_id: string;
title: string;
department: 'Volwassenen' | 'Jeugd' | 'Ouderen';
start_date: string;
}
interface IntakeListResponse {
intakes: Intake[];
total: number;
}
🗺️ Gedetailleerd Migratieplan
Fase 1: Intake API Ontwikkeling (3 SP)
Doel: Bouw Custom API voor Intake operaties
1.1 API Route Setup (1 SP)
Bestanden aan te maken:
app/api/intakes/
├── route.ts // GET (list), POST (create)
└── [intakeId]/
└── route.ts // GET, PUT, DELETE
Implementatie:
- Supabase client met RLS
- Error handling
- Input validatie (Zod schemas)
- Response formatting
Acceptatie Criteria:
- GET
/api/intakes?patientId={id}retourneert alle intakes - POST
/api/intakescreëert nieuwe intake - GET
/api/intakes/{id}retourneert specifieke intake - PUT
/api/intakes/{id}update intake - DELETE
/api/intakes/{id}verwijdert intake - Alle endpoints hebben error handling
- Input validatie werkt
1.2 Type Definitions (0.5 SP) ✅
Bestand: lib/types/intake.ts
Inhoud:
- ✅ Intake interface
- ✅ CreateIntakeInput, UpdateIntakeInput types
- ✅ IntakeListResponse type
- ✅ Zod validation schemas
1.3 Server Actions Refactor (1.5 SP) ✅
Bestand: app/epd/patients/[id]/intakes/actions.ts
Wijzigingen:
// VOOR (Supabase direct):
const { data } = await supabase.from('intakes').select('*')
// NA (API call):
const response = await fetch(`/api/intakes?patientId=${patientId}`)
const data = await response.json()
Updates:
- ✅
getIntakesByClientId→getIntakesByPatientId - ✅
createIntake- gebruik POST/api/intakes - ✅
getIntakeById- gebruik GET/api/intakes/{id} - ✅ Cookies worden doorgegeven aan fetch calls
- ✅ Error handling voor auth redirects
Complexiteit: Medium Risico: Laag
Fase 2: Component Migratie (3 SP)
Doel: Verplaats alle Intake componenten naar /patients/
2.1 Directory Structuur (0.5 SP) ✅
Creëer structuur:
app/epd/patients/[id]/intakes/
├── components/
│ ├── intake-card.tsx ✅
│ ├── intake-list.tsx ✅
│ └── new-intake-form.tsx ✅
├── [intakeId]/
│ ├── components/
│ │ ├── intake-header.tsx ✅
│ │ └── intake-tabs.tsx ✅
│ ├── layout.tsx ✅
│ └── page.tsx ✅
├── new/
│ └── page.tsx ✅
└── actions.ts ✅
2.2 Kopieer en Pas Aan (2 SP) ✅
Voor elk component:
- ✅ Kopieer van
/clients/[id]/intakes/naar/patients/[id]/intakes/ - ✅ Update imports:
clientId→patientId(props en variabelen)/epd/clients/→/epd/patients/(routes)- Type imports naar
@/lib/types/intake
- ✅ Update server action calls (gebruik nieuwe actions.ts)
- ✅ Import paden gecorrigeerd
Specifieke wijzigingen:
intake-list.tsx:
- ✅ Props:
clientId→patientId - ✅ Link urls:
/clients/→/patients/
new-intake-form.tsx:
- ✅ Form field:
patient_idipvclient_id - ✅ Redirect:
/patients/ipv/clients/
intake-header.tsx:
- ✅ Breadcrumb:
/patients/ipv/clients/ - ✅ Type imports gecorrigeerd
Complexiteit: Laag (copy-paste + find/replace) Risico: Laag
2.3 Verwijder Placeholder (0.5 SP) ✅
Bestand: app/epd/patients/[id]/intake/page.tsx
Actie: ✅ Verwijder placeholder, vervang door redirect:
export default async function IntakeRedirect({ params }) {
const { id } = await params;
redirect(`/epd/patients/${id}/intakes`);
}
Fase 3: Navigatie Integratie (2 SP)
Doel: Integreer Intake tab in patient navigatie
3.1 Update ClientSidebar (1 SP) ✅
Bestand: app/epd/patients/[id]/components/client-sidebar.tsx
Wijzigingen:
- ✅ Maak "Intake" tab interactief (verwijder placeholder styling)
- ✅ Link naar
/patients/{id}/intakes - ✅ Active state logic voor subroutes
- Optioneel: Badge met aantal openstaande intakes (nog niet geïmplementeerd)
3.2 Update Dashboard (1 SP) ✅
Bestand: app/epd/patients/[id]/page.tsx
Wijzigingen:
- ✅ Update "Volgende stappen" sectie
- ✅ Verwijs naar intake functionaliteit
- ✅ Intake quick action card linkt naar nieuwe route
- ✅ Optioneel: Toon recent intake in dashboard (geïmplementeerd)
Complexiteit: Laag Risico: Laag
Fase 4: Route Consolidatie (2 SP)
Doel: Deprecate /clients/ en setup redirects
4.1 Catch-all Redirect (1 SP) ✅
Bestand: app/epd/clients/[...path]/route.ts (nieuw)
Implementatie:
import { redirect } from 'next/navigation';
import { NextRequest } from 'next/server';
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ path: string[] }> }
) {
const { path } = await params;
const searchParams = request.nextUrl.searchParams;
const newPath = `/epd/patients/${path.join('/')}`;
const newUrl = new URL(newPath, request.url);
searchParams.forEach((value, key) => {
newUrl.searchParams.set(key, value);
});
redirect(newUrl.toString());
}
Test scenarios:
- ✅
/epd/clients→/epd/patients(viapage.tsxredirect) - ✅
/epd/clients/{id}→/epd/patients/{id} - ✅
/epd/clients/{id}/intakes→/epd/patients/{id}/intakes - ✅
/epd/clients/{id}/intakes/{intakeId}→/epd/patients/{id}/intakes/{intakeId} - ✅ Query parameters worden behouden
4.2 Archive Old Code (0.5 SP) ✅
Acties:
- ✅
mkdir -p app/epd/_archive - ✅
mv app/epd/clients app/epd/_archive/clients_backup_20251122 - ✅ Redirect routes teruggeplaatst in
app/epd/clients/ - ✅ Oude code volledig gearchiveerd
4.3 Update Documentatie (0.5 SP) ✅
Bestanden bij te werken:
- ✅
docs/specs/UI/bouwplan-mini-epd-v1.0.md- Update/clients/naar/patients/ - ✅
CHANGELOG.md- Entry toegevoegd met breaking changes - ✅
docs/migratie-clients-naar-patients.md- Dit document bijgewerkt docs/bouwplan-mini-epd.md- Nog te updaten indien aanwezigREADME.md- Nog te updaten indien screenshots/links aanwezig
Changelog:
- ✅ Entry toegevoegd aan
CHANGELOG.mdmet breaking changes en mitigatie
Complexiteit: Laag Risico: Laag
Fase 5: Testing & Validatie (2 SP)
Doel: Verifieer feature parity en stabiliteit
5.1 Functionele Tests (1 SP)
Test Checklist:
Intake List:
- Navigeer naar
/patients/{id}/intakes - Lijst toont alle intakes voor patient
- Empty state toont bij geen intakes
- "Nieuwe intake" button werkt
Nieuwe Intake:
- Formulier opent via "Nieuwe intake" button
- Alle velden valideren correct
- Submit creëert intake in database
- Redirect naar intake lijst na succes
- Intake verschijnt in lijst
Intake Detail:
- Klik op intake card opent detail
- Header toont correcte titel, status, datums
- Tabs tonen (ook al zijn ze placeholder)
- Algemene informatie tab toont data
- Notities sectie werkt
Navigatie:
- Sidebar "Intake" tab is actief
- Breadcrumbs kloppen
- Terug naar patiënten werkt
Redirects:
/clients/{id}→/patients/{id}werkt/clients/{id}/intakes→/patients/{id}/intakeswerkt- Query parameters behouden blijven
5.2 API Tests (0.5 SP)
Test alle endpoints:
# List intakes
curl http://localhost:3000/api/intakes?patientId={uuid}
# Create intake
curl -X POST http://localhost:3000/api/intakes \
-H "Content-Type: application/json" \
-d '{"patient_id":"uuid","title":"Test","department":"Volwassenen","start_date":"2025-11-22"}'
# Get intake
curl http://localhost:3000/api/intakes/{intakeId}
# Update intake
curl -X PUT http://localhost:3000/api/intakes/{intakeId} \
-d '{"status":"Afgerond"}'
# Delete intake
curl -X DELETE http://localhost:3000/api/intakes/{intakeId}
Verifieer:
- Response status codes correct (200, 201, 404, etc.)
- Response bodies bevatten verwachte data
- Errors worden netjes afgehandeld
- RLS policies werken (unauthorized access blocked)
5.3 Regressie Tests (0.5 SP)
Verifieer bestaande functionaliteit:
- Patient list werkt nog
- Patient create/update/delete werkt nog
- Screening tab (placeholder) werkt nog
- Andere tabs onveranderd
Complexiteit: Medium Risico: Medium
📈 Story Point Breakdown
| Fase | Taak | SP | Complexiteit | Risico |
|---|---|---|---|---|
| 1.1 | API Route Setup | 1 | Medium | Laag |
| 1.2 | Type Definitions | 0.5 | Laag | Laag |
| 1.3 | Server Actions Refactor | 1.5 | Medium | Laag |
| 2.1 | Directory Structuur | 0.5 | Laag | Laag |
| 2.2 | Kopieer en Pas Aan | 2 | Laag | Laag |
| 2.3 | Verwijder Placeholder | 0.5 | Laag | Laag |
| 3.1 | Update ClientSidebar | 1 | Laag | Laag |
| 3.2 | Update Dashboard | 1 | Laag | Laag |
| 4.1 | Catch-all Redirect | 1 | Medium | Laag |
| 4.2 | Archive Old Code | 0.5 | Laag | Laag |
| 4.3 | Update Documentatie | 0.5 | Laag | Laag |
| 5.1 | Functionele Tests | 1 | Medium | Medium |
| 5.2 | API Tests | 0.5 | Medium | Medium |
| 5.3 | Regressie Tests | 0.5 | Laag | Laag |
| TOTAAL | 12 SP | Laag-Medium | Laag-Medium |
Geschatte Tijdsduur: 2-3 werkdagen (16-24 uur development tijd)
⚠️ Risico's en Mitigatie
| Risico | Impact | Waarschijnlijkheid | Mitigatie |
|---|---|---|---|
| Breaking changes in productie | Hoog | Laag | Redirects + grondige testing |
| Data inconsistentie (intakes) | Hoog | Laag | Gebruik dezelfde database tabel |
| Gemiste edge cases | Medium | Medium | Uitgebreide test checklist |
| Performance issues (API overhead) | Laag | Laag | Cache strategie, index optimization |
| Type errors na migratie | Medium | Medium | TypeScript strict mode, thorough testing |
🎯 Acceptatie Criteria (Definition of Done)
Functioneel:
- Alle Intake functionaliteit werkt in
/patients/route - Feature parity met originele
/clients/implementatie - Redirects werken voor alle
/clients/URLs - Geen broken links in applicatie
Technisch:
- API endpoints geïmplementeerd en werkend
- Type definitions compleet (
lib/types/intake.ts) - Error handling geïmplementeerd (inclusief auth redirects)
- Cookies worden correct doorgegeven aan API calls
- RLS policies getest (nog te valideren)
Testing:
- Basis functionele tests uitgevoerd (componenten werken)
- API tests uitgevoerd (endpoints werken)
- Import paden gecorrigeerd
- Geen TypeScript errors
- Volledige regressie tests (nog te doen)
Documentatie:
- Bouwplannen bijgewerkt (
bouwplan-mini-epd-v1.0.md) - CHANGELOG.md entry toegevoegd
- API documentatie beschikbaar (
docs/api/intakes-api.md) - Migration guide voor developers (dit document)
Cleanup:
- Oude
/clients/code gearchiveerd - Geen duplicate code
- Import paden gecorrigeerd
- Console warnings/errors check (nog te doen)
🚀 Implementatie Volgorde (Aanbevolen)
Dag 1: API Foundation
- Ochtend: Fase 1.1 + 1.2 (API routes + types)
- Middag: Fase 1.3 (Server actions refactor)
- Eind dag: Fase 5.2 (API tests)
Deliverable: Werkende Intake API
Dag 2: Component Migratie
- Ochtend: Fase 2.1 + 2.2 (Kopieer componenten)
- Middag: Fase 2.3 + 3.1 + 3.2 (Navigatie integratie)
- Eind dag: Fase 5.1 (Functionele tests)
Deliverable: Werkende Intake module in /patients/
Dag 3: Consolidatie & Cleanup
- Ochtend: Fase 4.1 + 4.2 (Redirects + archive)
- Middag: Fase 4.3 (Documentatie)
- Eind dag: Fase 5.3 (Regressie tests)
Deliverable: Volledige migratie afgerond
📦 Deliverables Checklist
Code:
/app/api/intakes/- API routes (GET, POST)/app/api/intakes/[intakeId]/- API routes (GET, PUT, DELETE)/app/epd/patients/[id]/intakes/- Volledige module/app/epd/clients/[...path]/route.ts- Redirect (catch-all)/app/epd/clients/page.tsx- Root redirect/lib/types/intake.ts- Type definitions
Documentatie:
docs/migratie-clients-naar-patients.md- Dit document (bijgewerkt)docs/api/intakes-api.md- API documentatie- Updated bouwplannen (
bouwplan-mini-epd-v1.0.md) - CHANGELOG.md entry
Tests:
- Test rapport met resultaten
- Screenshot van werkende features
- Performance metrics (optioneel)
Archief:
/app/epd/_archive/clients_backup_20251122/- Oude code
🔮 Toekomstige Verbeteringen (Out of Scope)
FHIR Compliance:
- Migreer Custom Intake API naar FHIR Encounter
- Mapping van
intakestabel naar FHIR resources - Implementeer FHIR search parameters
Features:
- Intake status workflow (bezig → afgerond met validaties)
- Intake templates per afdeling
- Notities met rich text editor
- Document attachments per intake
- Intake duplicatie/klonen
- Bulk operations (meerdere intakes tegelijk)
Performance:
- API response caching
- Optimistic UI updates
- Lazy loading van intake details
- Pagination voor intake lists (bij >50 intakes)
Analytics:
- Intake completion metrics
- Average intake duration per afdeling
- Psychologist workload dashboard
📞 Ondersteuning en Vragen
Contact:
- Developer: Colin Lit
- Email: colin@ikbenlit.nl
Resources:
- Bouwplan Screening & Intake:
docs/specs/screening-intake/bouwplan-screening-intake-v1.0.md - FHIR Bouwplan:
docs/bouwplan-pragmatisch-fhir.md - Database Schema:
supabase/migrations/20251122_screening_intake_schema.sql
Versie Historie:
- v1.0 (2025-11-22): Initial migration plan met Custom API strategie