fix: toon gekoppelde ouders in de gebruikerslijst van de school (v0.3.59-beta)
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 28s
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 28s
- Ouders (school_id NULL) vielen buiten elke schoolgescopeerde gebruikerslijst; /admin/users levert nu ook de ouders die aan leerlingen van de school gekoppeld zijn (groepsleiding: alleen ouders van leerlingen uit eigen klassen) - Ouder-rijen tonen de namen van hun gekoppelde kinderen (childNames) - Servertest voor de gekoppelde-ouders-lijst Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0149FgUQvuwxKKEdvQGmNngF
This commit is contained in:
parent
5f9ffbfdde
commit
42fc323ab1
5 changed files with 52 additions and 2 deletions
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
||||||
0.3.58-beta
|
0.3.59-beta
|
||||||
|
|
|
||||||
|
|
@ -205,6 +205,10 @@
|
||||||
if(u.role==="pupil"){
|
if(u.role==="pupil"){
|
||||||
const cls = CLASSES.find(c=>Number(c.id)===Number(u.classId));
|
const cls = CLASSES.find(c=>Number(c.id)===Number(u.classId));
|
||||||
row.appendChild(h("span","am-role", T(roleKey(u.role)) + (cls ? ` · ${cls.name}` : "")));
|
row.appendChild(h("span","am-role", T(roleKey(u.role)) + (cls ? ` · ${cls.name}` : "")));
|
||||||
|
}else if(u.role==="parent"){
|
||||||
|
/* de school ziet bij elke ouder direct om welke kinderen het gaat */
|
||||||
|
const kids = (u.childNames||[]).join(", ");
|
||||||
|
row.appendChild(h("span","am-role", T(roleKey(u.role)) + (kids ? ` · 🧒 ${kids}` : "")));
|
||||||
}else{
|
}else{
|
||||||
const names = (u.classIds||[])
|
const names = (u.classIds||[])
|
||||||
.map(cid=>CLASSES.find(c=>Number(c.id)===Number(cid))?.name)
|
.map(cid=>CLASSES.find(c=>Number(c.id)===Number(cid))?.name)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
/* version — shown until /api/version resolves (or if the fetch fails, e.g. offline).
|
/* version — shown until /api/version resolves (or if the fetch fails, e.g. offline).
|
||||||
Kept in sync by hand with the VERSION file at the repo root on every release. */
|
Kept in sync by hand with the VERSION file at the repo root on every release. */
|
||||||
const VERSION = "0.3.58-beta";
|
const VERSION = "0.3.59-beta";
|
||||||
(function(){
|
(function(){
|
||||||
const tag = document.getElementById("verTag");
|
const tag = document.getElementById("verTag");
|
||||||
tag.textContent = "v"+VERSION;
|
tag.textContent = "v"+VERSION;
|
||||||
|
|
|
||||||
25
src/api.js
25
src/api.js
|
|
@ -389,11 +389,20 @@ export default async function api(app) {
|
||||||
// teacher de eigen school maar leerlingen alleen uit de eigen klas(sen).
|
// teacher de eigen school maar leerlingen alleen uit de eigen klas(sen).
|
||||||
app.get('/admin/users', async (req, reply) => {
|
app.get('/admin/users', async (req, reply) => {
|
||||||
need(req, reply, PERMISSIONS['users.manage']);
|
need(req, reply, PERMISSIONS['users.manage']);
|
||||||
|
// Ouders hebben school_id NULL en vallen daardoor buiten elke schoollijst;
|
||||||
|
// een school ziet wél de ouders die aan haar eigen leerlingen gekoppeld zijn.
|
||||||
|
const linkedParents = async (schoolId, extra = '', params = []) => (await pool.query(
|
||||||
|
`SELECT DISTINCT p.* FROM users p
|
||||||
|
JOIN parent_children pc ON pc.parent_id = p.id
|
||||||
|
JOIN users k ON k.id = pc.pupil_id
|
||||||
|
WHERE k.school_id = $1 ${extra} ORDER BY lower(p.username)`,
|
||||||
|
[schoolId, ...params])).rows;
|
||||||
let rows;
|
let rows;
|
||||||
if (req.user.allRoles.includes('super')) {
|
if (req.user.allRoles.includes('super')) {
|
||||||
rows = (req.query.school
|
rows = (req.query.school
|
||||||
? await pool.query('SELECT * FROM users WHERE school_id = $1 ORDER BY role, lower(username)', [req.query.school])
|
? await pool.query('SELECT * FROM users WHERE school_id = $1 ORDER BY role, lower(username)', [req.query.school])
|
||||||
: await pool.query('SELECT * FROM users ORDER BY school_id NULLS FIRST, role, lower(username)')).rows;
|
: await pool.query('SELECT * FROM users ORDER BY school_id NULLS FIRST, role, lower(username)')).rows;
|
||||||
|
if (req.query.school) rows = rows.concat(await linkedParents(req.query.school));
|
||||||
} else if (teacherOnly(req)) {
|
} else if (teacherOnly(req)) {
|
||||||
rows = (await pool.query(
|
rows = (await pool.query(
|
||||||
`SELECT * FROM users
|
`SELECT * FROM users
|
||||||
|
|
@ -401,8 +410,11 @@ export default async function api(app) {
|
||||||
AND (role != 'pupil' OR class_id IN (SELECT class_id FROM class_teachers WHERE user_id = $2))
|
AND (role != 'pupil' OR class_id IN (SELECT class_id FROM class_teachers WHERE user_id = $2))
|
||||||
ORDER BY role, lower(username)`,
|
ORDER BY role, lower(username)`,
|
||||||
[req.user.school_id, req.user.id])).rows;
|
[req.user.school_id, req.user.id])).rows;
|
||||||
|
rows = rows.concat(await linkedParents(req.user.school_id,
|
||||||
|
'AND k.class_id IN (SELECT class_id FROM class_teachers WHERE user_id = $2)', [req.user.id]));
|
||||||
} else {
|
} else {
|
||||||
rows = (await pool.query('SELECT * FROM users WHERE school_id = $1 ORDER BY role, lower(username)', [req.user.school_id])).rows;
|
rows = (await pool.query('SELECT * FROM users WHERE school_id = $1 ORDER BY role, lower(username)', [req.user.school_id])).rows;
|
||||||
|
rows = rows.concat(await linkedParents(req.user.school_id));
|
||||||
}
|
}
|
||||||
// extra stafrollen per gebruiker erbij ophalen (in één keer, niet per rij)
|
// extra stafrollen per gebruiker erbij ophalen (in één keer, niet per rij)
|
||||||
const roleRows = rows.length
|
const roleRows = rows.length
|
||||||
|
|
@ -422,11 +434,24 @@ export default async function api(app) {
|
||||||
const key = String(r.user_id);
|
const key = String(r.user_id);
|
||||||
classesByUser.set(key, [...(classesByUser.get(key) || []), Number(r.class_id)]);
|
classesByUser.set(key, [...(classesByUser.get(key) || []), Number(r.class_id)]);
|
||||||
});
|
});
|
||||||
|
// kindernamen bij ouder-rijen, zodat de school ziet wie bij wie hoort
|
||||||
|
const parentIds = rows.filter((u) => u.role === 'parent').map((u) => u.id);
|
||||||
|
const kidRows = parentIds.length
|
||||||
|
? (await pool.query(
|
||||||
|
`SELECT pc.parent_id, k.display_name, k.username FROM parent_children pc
|
||||||
|
JOIN users k ON k.id = pc.pupil_id WHERE pc.parent_id = ANY($1)`, [parentIds])).rows
|
||||||
|
: [];
|
||||||
|
const kidsByParent = new Map();
|
||||||
|
kidRows.forEach((r) => {
|
||||||
|
const key = String(r.parent_id);
|
||||||
|
kidsByParent.set(key, [...(kidsByParent.get(key) || []), r.display_name || r.username]);
|
||||||
|
});
|
||||||
return { users: rows.map((u) => ({
|
return { users: rows.map((u) => ({
|
||||||
...publicUser({ ...u, extra_roles: extraByUser.get(String(u.id)) || [] }),
|
...publicUser({ ...u, extra_roles: extraByUser.get(String(u.id)) || [] }),
|
||||||
linkCode: u.link_code || null,
|
linkCode: u.link_code || null,
|
||||||
everLoggedIn: !!u.last_login_at,
|
everLoggedIn: !!u.last_login_at,
|
||||||
classIds: classesByUser.get(String(u.id)) || [],
|
classIds: classesByUser.get(String(u.id)) || [],
|
||||||
|
...(u.role === 'parent' ? { childNames: kidsByParent.get(String(u.id)) || [] } : {}),
|
||||||
})) };
|
})) };
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -288,6 +288,27 @@ test('afwijzen verwijdert alleen het verzoek; andere school mag niet afhandelen'
|
||||||
await app.close(); await app2.close();
|
await app.close(); await app2.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('schoolbeheerder ziet gekoppelde ouders (met kindernamen) in de gebruikerslijst', async () => {
|
||||||
|
const { app } = await makeApp({ user: admin, respond: (sql) => {
|
||||||
|
if (sql.startsWith('SELECT * FROM users WHERE school_id')) return { rows: [
|
||||||
|
{ id: 50, username: 'lena', role: 'pupil', school_id: 2, class_id: 5 },
|
||||||
|
] };
|
||||||
|
if (sql.includes('JOIN parent_children pc ON pc.parent_id = p.id')) return { rows: [
|
||||||
|
{ id: 20, username: 'ouder', display_name: 'Ouder', role: 'parent', school_id: null, class_id: null },
|
||||||
|
] };
|
||||||
|
if (sql.includes('FROM parent_children pc') && sql.includes('WHERE pc.parent_id = ANY')) return { rows: [
|
||||||
|
{ parent_id: 20, display_name: 'Lena', username: 'lena' },
|
||||||
|
] };
|
||||||
|
return undefined;
|
||||||
|
} });
|
||||||
|
const res = await app.inject({ method: 'GET', url: '/api/admin/users', cookies });
|
||||||
|
assert.equal(res.statusCode, 200, res.body);
|
||||||
|
const p = res.json().users.find((u) => u.role === 'parent');
|
||||||
|
assert.ok(p, 'ouder hoort in de schoollijst te staan');
|
||||||
|
assert.deepEqual(p.childNames, ['Lena']);
|
||||||
|
await app.close();
|
||||||
|
});
|
||||||
|
|
||||||
test('thuiskind logt voortgang met de gekoppelde ouder als leerkracht', async () => {
|
test('thuiskind logt voortgang met de gekoppelde ouder als leerkracht', async () => {
|
||||||
const { app, calls } = await makeApp({ user: homeChild, respond: (sql) => {
|
const { app, calls } = await makeApp({ user: homeChild, respond: (sql) => {
|
||||||
if (sql.includes('FROM parent_children WHERE pupil_id')) return { rows: [{ parent_id: 20 }] };
|
if (sql.includes('FROM parent_children WHERE pupil_id')) return { rows: [{ parent_id: 20 }] };
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue