Voeg Module 4 experts, waardebepalingspagina en projectverbeteringen toe

Experts (Module 4)
- Pagina Experts (CRM): experts uitnodigen per e-mail (zonder SMTP wordt de
  link getoond), rondes koppelen, opnieuw uitnodigen, verwijderen en
  "Inloggen als" (alleen lezen) om mee te kijken
- Uitnodigingspagina waar een expert zijn wachtwoord instelt (token 7 dagen
  geldig, eenmalig, alleen als hash opgeslagen)
- Expertpagina: projecten in waardebepaling uit de eigen rondes, project plan
  lezen en eigen beoordeling geven (3 scores, advies Bonus/Malus, toelichting)
- Rollen admin/expert: alle beheerpagina's en -acties controleren de rol
- Rikx calculatie gebruikt de beoordelingen van de experts in plaats van de
  handmatige expertkolommen; experts koppelen ook vanaf de rondepagina

Waardebepaling projecten
- Nieuwe pagina met projecten in waardebepaling op kosten/impact (laag naar
  hoog), filter op ronde, Rikx Waarde-veld dat live doorrekent en opslaat in
  de ronde, en een 75%-lijn bij een gekozen ronde

Projecten
- Rikx calculatie als tab, zichtbaar vanaf status Waardebepaling, met keuze
  rekenmethode (Arbeid; Jeugd nog niet beschikbaar) en beoordeling
  goedgekeurd/afgekeurd met reden
- Impactmaker gekoppeld aan een sociaal ondernemer
- Opslaan-knop bovenaan; ongeldige velden op een verborgen tab worden getoond
- Project toevoegen maakt direct een project aan; aparte pagina verwijderd
- Waarderingssystematiek Rikx juni 2023 toegevoegd aan _doc

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-08 14:42:22 +02:00
parent 9f0bd87c40
commit 53bf667f54
41 changed files with 2075 additions and 357 deletions
+59 -3
View File
@@ -52,8 +52,40 @@ function migrate(db: DatabaseSync) {
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);
-- Expertscores per project voor de Rikx calculatie (Impactscore M).
-- Voorlopig handmatig ingevuld per expertnummer; Module 4 koppelt dit later aan expert-logins.
-- Module 4: experts zijn gebruikers met role = 'expert'.
-- Rondes waarvan een expert de projecten in waardebepaling ziet
CREATE TABLE IF NOT EXISTS expert_rondes (
expert_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
ronde_id INTEGER NOT NULL REFERENCES rikx_rondes(id) ON DELETE CASCADE,
PRIMARY KEY (expert_id, ronde_id)
);
-- Uitnodigingen: alleen de SHA-256-hash van het token wordt bewaard
CREATE TABLE IF NOT EXISTS expert_uitnodigingen (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
token_hash TEXT NOT NULL UNIQUE,
expires_at TEXT NOT NULL,
used_at TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
-- Beoordeling van een project door een expert; Impactscore M = gemiddelde over de experts
CREATE TABLE IF NOT EXISTS expert_beoordelingen (
project_id INTEGER NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
expert_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
duur_impact REAL,
levensdomeinen REAL,
basissituatie REAL,
bonus_malus_advies TEXT,
toelichting TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
PRIMARY KEY (project_id, expert_id)
);
-- Oude, handmatig ingevulde expertscores (expert 1–3). Niet meer gebruikt sinds de expertbeoordelingen;
-- de tabel blijft bestaan omdat hij in productie al gegevens kan bevatten.
CREATE TABLE IF NOT EXISTS project_expert_scores (
project_id INTEGER NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
expert INTEGER NOT NULL,
@@ -73,9 +105,16 @@ function migrate(db: DatabaseSync) {
addColumnIfMissing(db, "projects", "financiering_extern_pct", "REAL");
// Overige project plan-velden als JSON; definities in lib/projectPlan.ts
addColumnIfMissing(db, "projects", "project_plan", "TEXT");
// Stap "Rikx calculatie" (alleen zichtbaar als rikx_calculatie_aan = 1)
// Tab "Rikx calculatie". rikx_calculatie_aan is de oude schuif (niet meer gebruikt;
// de tab is nu zichtbaar vanaf status waardebepaling) en blijft staan omdat hij al in productie bestaat.
addColumnIfMissing(db, "projects", "rikx_calculatie_aan", "INTEGER NOT NULL DEFAULT 0");
addColumnIfMissing(db, "projects", "rekenmethode", "TEXT NOT NULL DEFAULT 'arbeid'");
addColumnIfMissing(db, "projects", "bonus_malus", "TEXT");
// Beoordeling onderaan de tab Rikx calculatie: 'goedgekeurd', 'afgekeurd' of NULL (nog niet beoordeeld)
addColumnIfMissing(db, "projects", "beoordeling", "TEXT");
addColumnIfMissing(db, "projects", "beoordeling_reden", "TEXT");
// Impactmaker: de sociaal ondernemer (accounts.id in de platformdatabase; geen foreign key, andere database)
addColumnIfMissing(db, "projects", "impactmaker_account_id", "INTEGER");
addColumnIfMissing(db, "projects", "ronde_id", "INTEGER REFERENCES rikx_rondes(id) ON DELETE SET NULL");
db.exec("CREATE INDEX IF NOT EXISTS idx_projects_ronde ON projects(ronde_id)");
}
@@ -131,7 +170,11 @@ export type Project = {
financiering_extern_pct: number | null;
project_plan: string | null;
rikx_calculatie_aan: number;
rekenmethode: string;
bonus_malus: string | null;
beoordeling: string | null;
beoordeling_reden: string | null;
impactmaker_account_id: number | null;
ronde_id: number | null;
created_at: string;
updated_at: string;
@@ -140,6 +183,19 @@ export type Project = {
// Project met het label van de gekoppelde Rikx Ronde (voor overzichten)
export type ProjectListItem = Project & { ronde_label: string | null };
export type ExpertBeoordeling = {
project_id: number;
expert_id: number;
duur_impact: number | null;
levensdomeinen: number | null;
basissituatie: number | null;
bonus_malus_advies: string | null;
toelichting: string | null;
created_at: string;
updated_at: string;
};
// Oude handmatige scores (niet meer gebruikt)
export type ProjectExpertScore = {
project_id: number;
expert: number;