Match-lås-fiksen er ferdig og verifisert. En liten, kirurgisk endring i app/routers/scoring.py: begge skrive-endepunktene (submit_hole_score, submit_hole_result) sjekker nå match.points_side_a IS NOT NULL (allerede et pålitelig signal for "avgjort", ingen ny kolonne/migrasjon nødvendig) og avviser med 409 før noen upsert kjøres — både for nye hull og korrigering av allerede talte hull.
Alle 5 punktene i planen bestått mot scratch-databasen:
Gjenskapte en avgjort match ("10&8 (A)")
Nytt hull (14) på avgjort match → 409 ✓
Korrigering av allerede talt hull (1) → 409 ✓
GET scorecard fortsatt leselig for avgjort match ✓
Fersk, ikke-avgjort match tar fortsatt imot hull normalt ✓
This commit is contained in:
parent
b3659cc953
commit
c521641c2e
3 changed files with 29 additions and 17 deletions
|
|
@ -84,7 +84,9 @@
|
||||||
"Bash(curl -s -w '\\\\n[HTTP %{http_code}]\\\\n' -X POST -H 'X-Debug-User-Id: a1000000-0000-0000-0000-000000000001' -H 'Content-Type: application/json' -d '{\"team_side\":\"a\",\"match_participant_id\":\"c81ed8c5-31f6-468f-8c10-8eba6d637fc3\",\"hole_number\":1,\"gross_strokes\":6}' http://127.0.0.1:8099/orgs/11111111-1111-1111-1111-111111111111/matches/aebcd63e-f452-4326-8a94-89bde1ace11d/hole-scores)",
|
"Bash(curl -s -w '\\\\n[HTTP %{http_code}]\\\\n' -X POST -H 'X-Debug-User-Id: a1000000-0000-0000-0000-000000000001' -H 'Content-Type: application/json' -d '{\"team_side\":\"a\",\"match_participant_id\":\"c81ed8c5-31f6-468f-8c10-8eba6d637fc3\",\"hole_number\":1,\"gross_strokes\":6}' http://127.0.0.1:8099/orgs/11111111-1111-1111-1111-111111111111/matches/aebcd63e-f452-4326-8a94-89bde1ace11d/hole-scores)",
|
||||||
"Bash(curl -s -w '\\\\n[HTTP %{http_code}]\\\\n' -X POST -H 'X-Debug-User-Id: b2000000-0000-0000-0000-000000000002' -H 'Content-Type: application/json' -d '{\"team_side\":\"a\",\"match_participant_id\":\"c81ed8c5-31f6-468f-8c10-8eba6d637fc3\",\"hole_number\":16,\"gross_strokes\":5}' http://127.0.0.1:8099/orgs/11111111-1111-1111-1111-111111111111/matches/aebcd63e-f452-4326-8a94-89bde1ace11d/hole-scores)",
|
"Bash(curl -s -w '\\\\n[HTTP %{http_code}]\\\\n' -X POST -H 'X-Debug-User-Id: b2000000-0000-0000-0000-000000000002' -H 'Content-Type: application/json' -d '{\"team_side\":\"a\",\"match_participant_id\":\"c81ed8c5-31f6-468f-8c10-8eba6d637fc3\",\"hole_number\":16,\"gross_strokes\":5}' http://127.0.0.1:8099/orgs/11111111-1111-1111-1111-111111111111/matches/aebcd63e-f452-4326-8a94-89bde1ace11d/hole-scores)",
|
||||||
"Bash(curl -s -w '\\\\n[HTTP %{http_code}]\\\\n' -H 'X-Debug-User-Id: __TRACKED_VAR__' __TRACKED_VAR__/orgs/__TRACKED_VAR__/matches/__TRACKED_VAR__/scorecard)",
|
"Bash(curl -s -w '\\\\n[HTTP %{http_code}]\\\\n' -H 'X-Debug-User-Id: __TRACKED_VAR__' __TRACKED_VAR__/orgs/__TRACKED_VAR__/matches/__TRACKED_VAR__/scorecard)",
|
||||||
"Bash(rm -f __TRACKED_VAR__/002_scratch.sql __TRACKED_VAR__/002_wrapper.sql __TRACKED_VAR__/hr_ids.env)"
|
"Bash(rm -f __TRACKED_VAR__/002_scratch.sql __TRACKED_VAR__/002_wrapper.sql __TRACKED_VAR__/hr_ids.env)",
|
||||||
|
"Bash(python3 -m py_compile /opt/teecup/app/routers/scoring.py)",
|
||||||
|
"Bash(rm -f __TRACKED_VAR__/002_scratch.sql __TRACKED_VAR__/002_wrapper.sql __TRACKED_VAR__/lock_ids.env)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -50,19 +50,19 @@
|
||||||
upsert (`ON CONFLICT ... DO UPDATE`), så en korrigering er ikke skilt fra en
|
upsert (`ON CONFLICT ... DO UPDATE`), så en korrigering er ikke skilt fra en
|
||||||
førstegangs-innføring. Ingen godkjenning fra motstanderen, ingen audit-trail
|
førstegangs-innføring. Ingen godkjenning fra motstanderen, ingen audit-trail
|
||||||
(forrige verdi overskrives sporløst).
|
(forrige verdi overskrives sporløst).
|
||||||
- **Hvem kan «lukke» en match eller turnering i dag:** INGEN eksplisitt
|
- **Match-lås ved avgjørelse: ✅ FIKSET 2026-07-16.** `submit_hole_score` og
|
||||||
handling finnes. En match sin `status_text`/poeng blir bare cached
|
`submit_hole_result` (`app/routers/scoring.py`) avviser nå med 409 («Matchen
|
||||||
automatisk når `handicap_engine.compute_match_state` sier den er avgjort
|
er avgjort og kan ikke lenger endres.») FØR upserten kjøres, hvis
|
||||||
(eller 18 hull er spilt) — det er ikke en handling noen utfører. **Reell,
|
`match.points_side_a IS NOT NULL` — dette er allerede et pålitelig,
|
||||||
ikke-teoretisk konsekvens oppdaget ved gjennomgang av dette:** ingenting
|
entydig signal siden kolonnen kun settes når `compute_match_state` sier
|
||||||
hindrer at noen fortsetter å legge inn hull ETTER at matchen matematisk er
|
matchen er avgjort. Gjelder BÅDE nye hull og korrigering av allerede talte
|
||||||
avgjort (f.eks. «10&5» ved hull 13) — blir hull 14-18 likevel registrert,
|
hull. Verifisert: hull etter avgjørelse avvist, korrigering av et
|
||||||
regnes de med i sekvensen ved neste rekalkulering og kan endre den cachede
|
talt hull avvist, GET scorecard fortsatt leselig, ikke-avgjorte matcher
|
||||||
marginen, siden `compute_match_state` teller alle sammenhengende avgjorte
|
upåvirket. **Bevisst IKKE bygget:** ingen manuell «avslutt match før den
|
||||||
hull, ikke bare fram til avgjørelsespunktet. Ingen sperre bygget for dette.
|
er avgjort»-handling (det grenser mot walkover under, fortsatt utsatt).
|
||||||
Turnering har et `status`-felt (draft/active/completed/archived) men
|
Turnering har et `status`-felt (draft/active/completed/archived) men
|
||||||
INGEN endepunkt endrer det ennå — organisator kan ikke markere en turnering
|
INGEN endepunkt endrer det ennå — organisator kan ikke markere en turnering
|
||||||
ferdig via API-et i dag.
|
ferdig via API-et i dag (eget, senere punkt).
|
||||||
- **Manglende WO/konsesjon:** hvis en side aldri stiller nok spillere
|
- **Manglende WO/konsesjon:** hvis en side aldri stiller nok spillere
|
||||||
(`match_participant`-antallet når aldri det økten krever), beregnes
|
(`match_participant`-antallet når aldri det økten krever), beregnes
|
||||||
handicap ALDRI (se `app/handicap.py`), og matchen kan derfor ALDRI få et
|
handicap ALDRI (se `app/handicap.py`), og matchen kan derfor ALDRI få et
|
||||||
|
|
@ -70,11 +70,9 @@
|
||||||
«gi bort hullet/matchen/turneringen»-mekanisme (walkover/konsesjon) i det
|
«gi bort hullet/matchen/turneringen»-mekanisme (walkover/konsesjon) i det
|
||||||
hele tatt ennå — verken datamodell eller endepunkt.
|
hele tatt ennå — verken datamodell eller endepunkt.
|
||||||
- **Avgjort 2026-07-16:**
|
- **Avgjort 2026-07-16:**
|
||||||
- **Match-lås bygges SNART** (neste lille runde) — eksplisitt handling som
|
- **Match-lås ved avgjørelse: ✅ bygget** — se eget punkt over. Automatisk
|
||||||
blokkerer videre hull-innlegging etter at en match er ferdig, og tetter
|
(ikke en handling noen utfører), så «hvem får låse» ble aldri et
|
||||||
«spøkelses-hull etter avgjørelse»-buggen over. Hvem som får lås/åpne den
|
spørsmål som trengte avklaring.
|
||||||
(kaptein? organisator? samme «rostret på laget»-bar som resten?) avgjøres
|
|
||||||
når den bygges — se punkt (a)/(b) fortsatt åpne under.
|
|
||||||
- **Walkover/konsesjon VENTER** til brukerroller (kaptein/organisator,
|
- **Walkover/konsesjon VENTER** til brukerroller (kaptein/organisator,
|
||||||
punktet over) er avgjort — å bygge den nå på dagens løse
|
punktet over) er avgjort — å bygge den nå på dagens løse
|
||||||
«rostret på laget»-grense betyr sannsynligvis å bygge den om senere.
|
«rostret på laget»-grense betyr sannsynligvis å bygge den om senere.
|
||||||
|
|
|
||||||
|
|
@ -218,6 +218,7 @@ async def submit_hole_score(
|
||||||
match = await conn.fetchrow(
|
match = await conn.fetchrow(
|
||||||
"""
|
"""
|
||||||
SELECT m.team_a_id::text AS team_a_id, m.team_b_id::text AS team_b_id,
|
SELECT m.team_a_id::text AS team_a_id, m.team_b_id::text AS team_b_id,
|
||||||
|
m.points_side_a::float AS points_side_a,
|
||||||
s.format, s.scoring_mode, s.hole_config::text AS hole_config
|
s.format, s.scoring_mode, s.hole_config::text AS hole_config
|
||||||
FROM match m JOIN session s ON s.id = m.session_id
|
FROM match m JOIN session s ON s.id = m.session_id
|
||||||
WHERE m.id = $1
|
WHERE m.id = $1
|
||||||
|
|
@ -226,6 +227,11 @@ async def submit_hole_score(
|
||||||
)
|
)
|
||||||
if match is None:
|
if match is None:
|
||||||
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Matchen finnes ikke.")
|
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Matchen finnes ikke.")
|
||||||
|
if match["points_side_a"] is not None:
|
||||||
|
raise HTTPException(
|
||||||
|
status.HTTP_409_CONFLICT,
|
||||||
|
detail="Matchen er avgjort og kan ikke lenger endres.",
|
||||||
|
)
|
||||||
if match["scoring_mode"] != "stroke":
|
if match["scoring_mode"] != "stroke":
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status.HTTP_400_BAD_REQUEST,
|
status.HTTP_400_BAD_REQUEST,
|
||||||
|
|
@ -338,6 +344,7 @@ async def submit_hole_result(
|
||||||
match = await conn.fetchrow(
|
match = await conn.fetchrow(
|
||||||
"""
|
"""
|
||||||
SELECT m.team_a_id::text AS team_a_id, m.team_b_id::text AS team_b_id,
|
SELECT m.team_a_id::text AS team_a_id, m.team_b_id::text AS team_b_id,
|
||||||
|
m.points_side_a::float AS points_side_a,
|
||||||
s.scoring_mode, s.hole_config::text AS hole_config
|
s.scoring_mode, s.hole_config::text AS hole_config
|
||||||
FROM match m JOIN session s ON s.id = m.session_id
|
FROM match m JOIN session s ON s.id = m.session_id
|
||||||
WHERE m.id = $1
|
WHERE m.id = $1
|
||||||
|
|
@ -346,6 +353,11 @@ async def submit_hole_result(
|
||||||
)
|
)
|
||||||
if match is None:
|
if match is None:
|
||||||
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Matchen finnes ikke.")
|
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Matchen finnes ikke.")
|
||||||
|
if match["points_side_a"] is not None:
|
||||||
|
raise HTTPException(
|
||||||
|
status.HTTP_409_CONFLICT,
|
||||||
|
detail="Matchen er avgjort og kan ikke lenger endres.",
|
||||||
|
)
|
||||||
if match["scoring_mode"] != "hole_result":
|
if match["scoring_mode"] != "hole_result":
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status.HTTP_400_BAD_REQUEST,
|
status.HTTP_400_BAD_REQUEST,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue