Require a capacity on every ticket wave #64

Merged
padreug merged 1 commit from fix/per-wave-capacity into main 2026-09-28 22:03:04 +00:00
Owner

Stacked on #63 — it depends on ticket waves existing, so it targets rebase/upstream-v1.6.8 rather than main. Merge #63 first and this retargets to a one-commit diff.

Refs #34, #62.

What was left

"Capacity is always required, there is no unlimited" was settled on #34 and enforced in #62 — but as a single number on the event. Since v1.6.8 capacity is per-wave and event.amount_tickets is a derived roll-up, so the rule had nothing holding it up where organisers actually set it: the wave dialog's capacity input had no minimum, and the backend checked nothing at all. The min="1" on the event-level field was the entire enforcement, and no API client is bound by an HTML attribute.

A zero-capacity wave can never be active — get_active_ticket_waves requires amount_tickets > 0 — so it is the wave-level form of exactly what #62 removed: an event that looks on sale but refuses every purchase, on the card and at checkout both.

The rule

_validate_wave_capacity runs on create and update. Two details that matter more than the check itself:

  • It validates ensure_ticket_waves(data), not the raw list, so an event submitted with no waves is checked through the primary wave it is about to be given rather than passing vacuously.
  • On an edit it only checks waves new to the event. Selling out is the legitimate route to zero, and rejecting it would make a sold-out event uneditable — including the legacy zero-capacity rows this rule exists to let organisers fix.

On update the check runs after the ownership check, so a non-owner still gets 403 rather than a validation message about someone else's event.

Frontend

The wave dialog's capacity input gains the min="1" and hint the event-level field already had, and a new wave opens at 1 instead of 0. That default uses ?? rather than || deliberately: a sold-out wave holds 0 and must keep showing it, or editing one would silently restore stock on save.

Also drops the stale 0 = unlimited / not ticketed contract still documented on CreateEvent.amount_tickets, which has not been true since #62.

Still open on #34

This closes the capacity-required half only, so #34 stays open. The display-total question is undecided — the upstream review on that issue narrowed it to either dropping "X of Y" from the card (no schema change, survives waves untouched) or storing capacity in extra JSON with an explicit tickets_total tag. aiolabs/webapp#143 is the client-side half and is waiting on that same decision.

Verification

6 new tests covering create, update, the synthesized-primary-wave path, a sold-out wave staying editable, a new wave added by an edit, and a legacy zero-capacity row. 120 pass. ruff, black, prettier clean; mypy error set unchanged from main's baseline.

Not yet exercised against a running instance — same aio-demo pass as #63 wants before v1.6.8-aio.1.

**Stacked on #63** — it depends on ticket waves existing, so it targets `rebase/upstream-v1.6.8` rather than `main`. Merge #63 first and this retargets to a one-commit diff. Refs #34, #62. ## What was left "Capacity is always required, there is no unlimited" was settled on #34 and enforced in #62 — but as a single number on the event. Since v1.6.8 capacity is per-wave and `event.amount_tickets` is a derived roll-up, so the rule had nothing holding it up where organisers actually set it: the wave dialog's capacity input had no minimum, and the backend checked nothing at all. The `min="1"` on the event-level field was the entire enforcement, and no API client is bound by an HTML attribute. A zero-capacity wave can never be active — `get_active_ticket_waves` requires `amount_tickets > 0` — so it is the wave-level form of exactly what #62 removed: an event that looks on sale but refuses every purchase, on the card and at checkout both. ## The rule `_validate_wave_capacity` runs on create and update. Two details that matter more than the check itself: - It validates `ensure_ticket_waves(data)`, not the raw list, so an event submitted with **no** waves is checked through the primary wave it is about to be given rather than passing vacuously. - On an edit it only checks waves **new** to the event. Selling out is the legitimate route to zero, and rejecting it would make a sold-out event uneditable — including the legacy zero-capacity rows this rule exists to let organisers fix. On update the check runs after the ownership check, so a non-owner still gets 403 rather than a validation message about someone else's event. ## Frontend The wave dialog's capacity input gains the `min="1"` and hint the event-level field already had, and a new wave opens at `1` instead of `0`. That default uses `??` rather than `||` deliberately: a sold-out wave holds `0` and must keep showing it, or editing one would silently restore stock on save. Also drops the stale `0 = unlimited / not ticketed` contract still documented on `CreateEvent.amount_tickets`, which has not been true since #62. ## Still open on #34 This closes the capacity-required half only, so **#34 stays open**. The display-total question is undecided — the upstream review on that issue narrowed it to either dropping "X of Y" from the card (no schema change, survives waves untouched) or storing capacity in `extra` JSON with an explicit `tickets_total` tag. `aiolabs/webapp#143` is the client-side half and is waiting on that same decision. ## Verification 6 new tests covering create, update, the synthesized-primary-wave path, a sold-out wave staying editable, a new wave added by an edit, and a legacy zero-capacity row. 120 pass. ruff, black, prettier clean; mypy error set unchanged from `main`'s baseline. Not yet exercised against a running instance — same aio-demo pass as #63 wants before `v1.6.8-aio.1`.
fix: require a capacity on every ticket wave
Some checks failed
lint.yml / fix: require a capacity on every ticket wave (pull_request) Failing after 0s
ecd05b4168
"Capacity is always required, there is no unlimited" was settled on #34
and enforced in #62 — but as a single number on the event. Since v1.6.8
capacity is per-wave and `event.amount_tickets` is a derived roll-up
(`sync_event_ticket_waves`), so the rule had nothing holding it up at
the level organisers actually set: the wave form's capacity input had no
minimum, and nothing on the backend checked one at all.

A zero-capacity wave can never be active — `get_active_ticket_waves`
requires `amount_tickets > 0` — so it is the wave-level form of exactly
what #62 removed: an event that looks on sale but refuses every
purchase, on the card and at checkout both.

`_validate_wave_capacity` now runs on create and update. Two details
that matter:

- it checks `ensure_ticket_waves(data)` rather than the raw list, so an
  event submitted with no waves is checked through the primary wave it
  is about to be given, not vacuously passed
- on an edit it only checks waves NEW to the event. Selling out is the
  legitimate route to zero, and rejecting it would make a sold-out event
  uneditable — including the legacy zero-capacity rows this rule exists
  to let organisers fix

Frontend: the wave dialog's capacity input gains the `min="1"` and hint
the event-level field already had, and a new wave opens at 1 rather than
0. That default uses `??`, not `||` — a sold-out wave holds 0 and must
keep showing it instead of silently regaining stock when saved.

Also drops the stale `0 = unlimited / not ticketed` contract still
documented on `CreateEvent.amount_tickets`, which has not been true
since #62.

6 new tests; 120 pass. ruff, black, prettier clean; mypy error set
unchanged from baseline.
padreug changed target branch from rebase/upstream-v1.6.8 to main 2026-09-28 22:02:55 +00:00
padreug deleted branch fix/per-wave-capacity 2026-09-28 22:03:04 +00:00
padreug referenced this pull request from a commit 2026-09-28 22:09:49 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
aiolabs/events!64
No description provided.