Gestion des référents depuis la fiche bénévole
Un poste avait bien un `lead_account_id`, lu par l'écran référent
(`/mobile-referent`) et par les droits d'écriture (`ensure_can_write_position`),
mais rien ne permettait de le renseigner : aucun écran ne l'exposait, et
l'API ne validait même pas que la valeur envoyée désignait un compte référent
existant de la bonne édition.
Backend :
- `PATCH /editions/{id}/accounts/{account_id}` (nouveau) : change le `role`
d'un compte, seulement entre LEAD et VOLUNTEER (lien magique dans les deux
cas) — 422 `account.role_change_not_allowed` dès que COORDINATOR est
impliqué (mot de passe en jeu, jamais touché ici). C'est ce qui permet de
faire passer le compte d'un bénévole en référent.
- `POST`/`PATCH /positions` : `lead_account_id` vérifié — doit désigner un
compte `Role.LEAD` de la même édition, sinon 422
`position.lead_must_be_referent_account` (silencieusement accepté
jusqu'ici, y compris un compte d'une autre édition ou du mauvais rôle).
Frontend (`ConfigVolunteers.tsx`) : nouvelle section « Référent des postes »
sur la fiche du bénévole, juste après l'accès de connexion — mêmes badges à
cocher que les vœux de poste/artiste. Cocher un poste crée ou met à niveau
(LEAD) l'accès de connexion du bénévole automatiquement ; décocher retire
seulement le poste, sans jamais rétrograder l'accès. Un bénévole dont le
compte est déjà COORDINATOR ne peut pas être désigné référent (il a déjà
accès à tout). Miroir complet côté mock (`accounts.update`,
`checkLeadAccount`) pour que la démonstration se comporte pareil.
Vérifié : 204 tests backend pertinents (14 nouveaux : 6 sur `PATCH accounts`,
8 sur la validation `lead_account_id`), ruff, alembic check ; typecheck/
lint/build/smoke frontend ; parcours complet testé en direct sur le jeu de
démo (création de compte, promotion LEAD, rejet COORDINATOR, affectation
référent) avant nettoyage des données de test.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uUsRg7XzopkCPWimwg9ZM
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
db11d9a3ba
commit
8fcc22115f
@@ -129,6 +129,16 @@ function resolveCompanionWishes(ids: string[], selfId?: string): string[] {
|
||||
return resolved;
|
||||
}
|
||||
|
||||
/** `lead_account_id` d'un poste : doit désigner un compte référent (LEAD)
|
||||
* existant — sinon le poste se retrouve sans référent utilisable. */
|
||||
function checkLeadAccount(leadAccountId: string | null | undefined): void {
|
||||
if (leadAccountId == null) return;
|
||||
const lead = state.accounts.find((a) => a.id === leadAccountId);
|
||||
if (!lead || lead.role !== 'LEAD') {
|
||||
throw new ApiError(422, 'position.lead_must_be_referent_account');
|
||||
}
|
||||
}
|
||||
|
||||
function shiftWindow(day: string, dp: DayPartRead): [string, string] {
|
||||
const endDay = dp.end_time <= dp.start_time ? addDays(day, 1) : day;
|
||||
return [`${day}T${dp.start_time}`, `${endDay}T${dp.end_time}`];
|
||||
@@ -859,6 +869,18 @@ export function createMockApi(): PlanningApi {
|
||||
state.accounts.push(account);
|
||||
return delay(account);
|
||||
},
|
||||
update: (_edition, id, body) => {
|
||||
const account = state.accounts.find((a) => a.id === id) ?? notFound();
|
||||
if (body.role != null && body.role !== account.role) {
|
||||
if (account.role === 'COORDINATOR' || body.role === 'COORDINATOR') {
|
||||
throw new ApiError(422, 'account.role_change_not_allowed');
|
||||
}
|
||||
account.role = body.role;
|
||||
account.auth_method = 'magic_link';
|
||||
}
|
||||
account.updated_at = touch();
|
||||
return delay(account);
|
||||
},
|
||||
remove: (_edition, id) => {
|
||||
state.accounts = state.accounts.filter((a) => a.id !== id);
|
||||
return delay(undefined);
|
||||
@@ -1090,6 +1112,7 @@ export function createMockApi(): PlanningApi {
|
||||
delay([...state.positions].sort((a, b) => a.name.localeCompare(b.name, 'fr'))),
|
||||
get: (_edition, id) => delay(state.positions.find((x) => x.id === id) ?? notFound()),
|
||||
create: (_edition, body) => {
|
||||
checkLeadAccount(body.lead_account_id);
|
||||
const now = touch();
|
||||
const position = {
|
||||
id: `p${state.seq++}`,
|
||||
@@ -1107,6 +1130,7 @@ export function createMockApi(): PlanningApi {
|
||||
},
|
||||
update: (_edition, id, body) => {
|
||||
const p = state.positions.find((x) => x.id === id) ?? notFound();
|
||||
if ('lead_account_id' in body) checkLeadAccount(body.lead_account_id);
|
||||
Object.assign(p, {
|
||||
...('name' in body ? { name: body.name } : {}),
|
||||
...('location' in body ? { location: body.location ?? null } : {}),
|
||||
|
||||
Reference in New Issue
Block a user