From a0163eaa16c39b78b73166520925751e316d3941 Mon Sep 17 00:00:00 2001 From: Erol Haagenrud Date: Fri, 21 Aug 2026 08:05:05 +0200 Subject: [PATCH] Ny runde-veiviseren: tre fikser (overleggsmeny, HCP-overstyring, statistikk-default) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - "Administrer runde"-dialogen lukker seg nå automatisk ved navigasjon til "Spillere og runde" (var stående åpen ved samme-rute-navigasjon). - HCP kan nå overstyres for en kontokoblet medspiller fra første stund i veiviseren, ikke bare senere via "Runde og runde". Backend sitt add_participant æret aldri en eksplisitt overstyring for user_id, ulikt PATCH-endepunktet (ADR-038-avvik, nå rettet). - Standard statistikknivå for en NY medspiller er nå "Kun slag" i stedet for å arve scoreførerens eget valg. Co-Authored-By: Claude Sonnet 5 --- app/routers/rounds.py | 8 +- .../components/ny-runde/step3-players.tsx | 36 ++++++- .../components/ny-runde/wizard-context.tsx | 7 ++ frontend/components/round-header.tsx | 7 +- tests/test_add_participant_hcp_override.py | 93 +++++++++++++++++++ 5 files changed, 144 insertions(+), 7 deletions(-) create mode 100644 tests/test_add_participant_hcp_override.py diff --git a/app/routers/rounds.py b/app/routers/rounds.py index 038f860..474e3c4 100644 --- a/app/routers/rounds.py +++ b/app/routers/rounds.py @@ -2955,7 +2955,13 @@ async def add_participant( "Spilleren du valgte har ikke fullført profilen sin (mangler kjønn) og kan ikke legges til ennå.", ) gender = target["gender"] - handicap_index = target["handicap_index"] + # Overstyring fra opprettelsesøyeblikket (2026-08-20, ADR-038 + # sier allerede "brukes til Course Handicap i enhver runde med + # mindre eksplisitt overstyrt" -- PATCH-en (update_participant + # under) har alltid tillatt dette for en lenket bruker, POST + # gjorde det uventet IKKE). Faller tilbake til profilens egen + # verdi når organisatoren ikke har oppgitt noe eksplisitt. + handicap_index = body.handicap_index if body.handicap_index is not None else target["handicap_index"] guest_name = None guest_first_name = None guest_last_name = None diff --git a/frontend/components/ny-runde/step3-players.tsx b/frontend/components/ny-runde/step3-players.tsx index 98af1d8..319a2a8 100644 --- a/frontend/components/ny-runde/step3-players.tsx +++ b/frontend/components/ny-runde/step3-players.tsx @@ -80,7 +80,13 @@ export function Step3Players() { {adding ? ( setAdding(false)} onAdd={addPlayer} @@ -230,10 +236,30 @@ function PlayerCard({ /> ) : player.hcpFromProfile && !isOwner ? ( - -
- {player.hcp != null ? `${player.hcp} · hentes fra profilen` : "Hentes fra profilen…"} -
+ // Redigerbar fra FØRSTE stund (2026-08-20 brukerfunn) -- var + // tidligere en read-only visning ("hentes fra profilen"), som + // tvang organisatoren til å legge til spilleren FØRST og + // overstyre HCP-en senere via "Runde og spillere" (der + // overstyring allerede fungerte, samme PATCH-felt). Forhånds- + // utfylt med profilverdien; urørt betyr fortsatt "bruk + // profilen", akkurat som før. + + { + const v = e.target.value + onChange({ hcp: v.trim() === "" ? null : Number.parseFloat(v.replace(",", ".")) }) + }} + className="max-w-[10rem]" + /> ) : null} diff --git a/frontend/components/ny-runde/wizard-context.tsx b/frontend/components/ny-runde/wizard-context.tsx index b2a5cb0..bfddb95 100644 --- a/frontend/components/ny-runde/wizard-context.tsx +++ b/frontend/components/ny-runde/wizard-context.tsx @@ -424,6 +424,13 @@ export function WizardProvider({ } if (p.accountId) { participantBody.user_id = p.accountId + // Overstyring fra FØRSTE stund (2026-08-20 brukerfunn) -- backend + // sin add_participant faller tilbake til spillerens profil-HCP + // når dette utelates/er null, akkurat som PATCH-en "Runde og + // runde"-panelet allerede bruker for samme overstyring senere. + // Uendret (`p.hcp` = profilverdien) sendes derfor trygt som + // no-op når organisatoren ikke har rørt feltet. + participantBody.handicap_index = p.hcp ?? null } else { participantBody.guest_first_name = p.firstName.trim() participantBody.guest_last_name = p.lastName.trim() || null diff --git a/frontend/components/round-header.tsx b/frontend/components/round-header.tsx index e9b9b6f..9df46c3 100644 --- a/frontend/components/round-header.tsx +++ b/frontend/components/round-header.tsx @@ -303,9 +303,14 @@ function ManageRoundDialog({ - {/* Reach the roster / team setup screen */} + {/* Reach the roster / team setup screen. `onClick={onClose}` (2026-08-20 + brukerfunn): denne lenken navigerer ofte til KUN en søkeparameter- + endring på samme rute (?tab=manage, se round-detail.tsx), som ikke + remounter RoundHeader -- uten dette ble dialogen stående åpen over + innholdet under, og måtte lukkes manuelt. */} diff --git a/tests/test_add_participant_hcp_override.py b/tests/test_add_participant_hcp_override.py new file mode 100644 index 0000000..4c84ae9 --- /dev/null +++ b/tests/test_add_participant_hcp_override.py @@ -0,0 +1,93 @@ +""" +Overstyring av HCP for en KONTOKOBLET medspiller ved selve opprettelsen +(2026-08-20, brukerfunn: "Ny runde"-veiviseren kunne ikke overstyre HCP +for en nylig lagt til medspiller fra første stund -- kun i etterkant via +PATCH .../participants/{id}, som allerede støttet det, jf. ADR-038: "HCP +... brukes til Course Handicap i enhver runde med mindre eksplisitt +overstyrt"). Kaller add_participant direkte, samme mønster som +test_flag_plant.py. +""" + +import uuid + +from app.auth import CurrentUser +from app.routers.rounds import ParticipantCreate, add_participant + +from tests.conftest import create_user + +import app.db as app_db + + +async def _setup_round_with_custom_course(owner_user_id: str) -> str: + """course_source='custom' (DB-only banedata, ingen nettverkskall mot + teeoff, ulikt 'teeoff'-fixturen conftest.create_round bruker) -- add_ + participant slår alltid opp banens rating når handicap_index er satt.""" + async with app_db.plain_connection() as conn: + personal_course_id = str(uuid.uuid4()) + await conn.execute( + "INSERT INTO personal_course (id, created_by_user_id, name) VALUES ($1, $2, 'Hjemmebanen')", + personal_course_id, owner_user_id, + ) + for n in range(1, 19): + await conn.execute( + "INSERT INTO personal_course_hole (personal_course_id, hole_number, par, stroke_index) " + "VALUES ($1, $2, 4, $3)", + personal_course_id, n, n, + ) + tee_id = str(uuid.uuid4()) + await conn.execute( + "INSERT INTO personal_course_tee (id, personal_course_id, name) VALUES ($1, $2, 'Gul')", + tee_id, personal_course_id, + ) + for gender in ("m", "f"): + await conn.execute( + "INSERT INTO personal_course_tee_rating " + "(personal_course_tee_id, gender, course_rating, slope_rating, par) " + "VALUES ($1, $2, 71.5, 125, 72)", + tee_id, gender, + ) + round_id = str(uuid.uuid4()) + await conn.execute( + """ + INSERT INTO round (id, owner_user_id, course_source, personal_course_id, + course_name_snapshot, tee_name_snapshot, played_at) + VALUES ($1, $2, 'custom', $3, 'Hjemmebanen', 'Gul', CURRENT_DATE) + """, + round_id, owner_user_id, personal_course_id, + ) + return round_id + + +async def test_add_participant_honors_explicit_handicap_override_for_linked_account(pool): + owner_id = await create_user() + target_id = await create_user(display_name="Medspiller") + async with app_db.plain_connection() as conn: + await conn.execute("UPDATE app_user SET gender = 'm', handicap_index = 18.4 WHERE id = $1", target_id) + round_id = await _setup_round_with_custom_course(owner_id) + + user = CurrentUser(user_id=owner_id) + result = await add_participant( + round_id, + ParticipantCreate(user_id=target_id, handicap_index=9.1), + user=user, + ) + assert result.handicap_index_snapshot == 9.1 + assert result.course_handicap_snapshot is not None + + +async def test_add_participant_falls_back_to_profile_handicap_when_no_override_given(pool): + """Regresjonsvern -- uendret oppførsel når organisatoren IKKE oppgir + noen overstyring (feltet stått urørt i veiviseren).""" + owner_id = await create_user() + target_id = await create_user(display_name="Medspiller") + async with app_db.plain_connection() as conn: + await conn.execute("UPDATE app_user SET gender = 'm', handicap_index = 18.4 WHERE id = $1", target_id) + round_id = await _setup_round_with_custom_course(owner_id) + + user = CurrentUser(user_id=owner_id) + result = await add_participant( + round_id, + ParticipantCreate(user_id=target_id), + user=user, + ) + assert result.handicap_index_snapshot == 18.4