events: whole-series booking + 9 tests
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 59s
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 59s
ev/book-series! / ev/cancel-series! apply a booking/cancel to every occurrence of one event in a window (RSVP the whole weekly class), returning per- occurrence (occ-key status) results; capacity still enforced per occurrence (some :booked, some :full), idempotent re-book (:already). ev/series-count, ev/series-booked. 341/341 green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -275,3 +275,55 @@
|
|||||||
((ev/would-time-conflict? b store actor occ)
|
((ev/would-time-conflict? b store actor occ)
|
||||||
{:status :time-conflict :actor actor :occ-key (ev-occ-key occ)})
|
{:status :time-conflict :actor actor :occ-key (ev-occ-key occ)})
|
||||||
(else (ev/book-occ! b store actor occ)))))
|
(else (ev/book-occ! b store actor occ)))))
|
||||||
|
|
||||||
|
;; ---- whole-series operations ----
|
||||||
|
;; Apply a booking action to every occurrence of one event in [ws, we) — e.g.
|
||||||
|
;; "RSVP to the whole weekly class". Returns a list of (occ-key status) results,
|
||||||
|
;; one per occurrence (empty if the event id is unknown).
|
||||||
|
(define
|
||||||
|
ev/book-series!
|
||||||
|
(fn
|
||||||
|
(b store actor event-id ws we)
|
||||||
|
(let
|
||||||
|
((ev (ev/event-by-id store event-id)))
|
||||||
|
(if
|
||||||
|
(nil? ev)
|
||||||
|
(list)
|
||||||
|
(map
|
||||||
|
(fn (occ) (list (ev-occ-key occ) (get (ev/book-occ! b store actor occ) :status)))
|
||||||
|
(ev-expand ev ws we))))))
|
||||||
|
|
||||||
|
;; Cancel `actor` from every occurrence of one event in [ws, we).
|
||||||
|
(define
|
||||||
|
ev/cancel-series!
|
||||||
|
(fn
|
||||||
|
(b store actor event-id ws we)
|
||||||
|
(let
|
||||||
|
((ev (ev/event-by-id store event-id)))
|
||||||
|
(if
|
||||||
|
(nil? ev)
|
||||||
|
(list)
|
||||||
|
(map
|
||||||
|
(fn (occ) (list (ev-occ-key occ) (get (ev/cancel! b (ev-occ-key occ) actor) :status)))
|
||||||
|
(ev-expand ev ws we))))))
|
||||||
|
|
||||||
|
;; How many statuses in a series-result list equal `status`.
|
||||||
|
(define
|
||||||
|
ev/series-count
|
||||||
|
(fn
|
||||||
|
(results status)
|
||||||
|
(len (filter (fn (r) (= (first (rest r)) status)) results))))
|
||||||
|
|
||||||
|
;; The occurrences of one event in [ws, we) that `actor` is booked into.
|
||||||
|
(define
|
||||||
|
ev/series-booked
|
||||||
|
(fn
|
||||||
|
(b store actor event-id ws we)
|
||||||
|
(let
|
||||||
|
((ev (ev/event-by-id store event-id)))
|
||||||
|
(if
|
||||||
|
(nil? ev)
|
||||||
|
(list)
|
||||||
|
(filter
|
||||||
|
(fn (occ) (ev-actor-booked? b (ev-occ-key occ) actor))
|
||||||
|
(ev-expand ev ws we))))))
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
{
|
{
|
||||||
"lang": "events",
|
"lang": "events",
|
||||||
"total_passed": 332,
|
"total_passed": 341,
|
||||||
"total_failed": 0,
|
"total_failed": 0,
|
||||||
"total": 332,
|
"total": 341,
|
||||||
"suites": [
|
"suites": [
|
||||||
{"name":"calendar","passed":51,"failed":0,"total":51},
|
{"name":"calendar","passed":51,"failed":0,"total":51},
|
||||||
{"name":"timezone","passed":17,"failed":0,"total":17},
|
{"name":"timezone","passed":17,"failed":0,"total":17},
|
||||||
{"name":"ical","passed":21,"failed":0,"total":21},
|
{"name":"ical","passed":21,"failed":0,"total":21},
|
||||||
{"name":"availability","passed":22,"failed":0,"total":22},
|
{"name":"availability","passed":22,"failed":0,"total":22},
|
||||||
{"name":"api","passed":32,"failed":0,"total":32},
|
{"name":"api","passed":41,"failed":0,"total":41},
|
||||||
{"name":"booking","passed":82,"failed":0,"total":82},
|
{"name":"booking","passed":82,"failed":0,"total":82},
|
||||||
{"name":"booking-notify","passed":11,"failed":0,"total":11},
|
{"name":"booking-notify","passed":11,"failed":0,"total":11},
|
||||||
{"name":"ticket","passed":31,"failed":0,"total":31},
|
{"name":"ticket","passed":31,"failed":0,"total":31},
|
||||||
@@ -17,5 +17,5 @@
|
|||||||
{"name":"federation","passed":29,"failed":0,"total":29},
|
{"name":"federation","passed":29,"failed":0,"total":29},
|
||||||
{"name":"integration","passed":8,"failed":0,"total":8}
|
{"name":"integration","passed":8,"failed":0,"total":8}
|
||||||
],
|
],
|
||||||
"generated": "2026-06-07T14:40:54+00:00"
|
"generated": "2026-06-07T15:20:08+00:00"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# events scoreboard
|
# events scoreboard
|
||||||
|
|
||||||
**332 / 332 passing** (0 failure(s)).
|
**341 / 341 passing** (0 failure(s)).
|
||||||
|
|
||||||
| Suite | Passed | Total | Status |
|
| Suite | Passed | Total | Status |
|
||||||
|-------|--------|-------|--------|
|
|-------|--------|-------|--------|
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
| timezone | 17 | 17 | ok |
|
| timezone | 17 | 17 | ok |
|
||||||
| ical | 21 | 21 | ok |
|
| ical | 21 | 21 | ok |
|
||||||
| availability | 22 | 22 | ok |
|
| availability | 22 | 22 | ok |
|
||||||
| api | 32 | 32 | ok |
|
| api | 41 | 41 | ok |
|
||||||
| booking | 82 | 82 | ok |
|
| booking | 82 | 82 | ok |
|
||||||
| booking-notify | 11 | 11 | ok |
|
| booking-notify | 11 | 11 | ok |
|
||||||
| ticket | 31 | 31 | ok |
|
| ticket | 31 | 31 | ok |
|
||||||
|
|||||||
@@ -319,6 +319,65 @@
|
|||||||
(ev/would-time-conflict? b store (quote zed) ob)
|
(ev/would-time-conflict? b store (quote zed) ob)
|
||||||
false))))))
|
false))))))
|
||||||
|
|
||||||
|
;; ---- whole-series booking ----
|
||||||
|
(define
|
||||||
|
ev-api-sr-run-all!
|
||||||
|
(fn
|
||||||
|
()
|
||||||
|
(let
|
||||||
|
((b (persist/open))
|
||||||
|
(store
|
||||||
|
(ev/schedule
|
||||||
|
(ev/empty)
|
||||||
|
(quote yoga)
|
||||||
|
(ev-dt 2026 6 1 18 0)
|
||||||
|
60
|
||||||
|
{:freq :weekly :byday (list 0 2) :count 4}
|
||||||
|
20))
|
||||||
|
(ws (ev-date 2026 6 1))
|
||||||
|
(we (ev-date 2026 7 1)))
|
||||||
|
(do
|
||||||
|
(let
|
||||||
|
((res (ev/book-series! b store (quote nia) (quote yoga) ws we)))
|
||||||
|
(do
|
||||||
|
(ev-api-check! "series booking covers all four occurrences" (len res) 4)
|
||||||
|
(ev-api-check! "all occurrences booked" (ev/series-count res :booked) 4)
|
||||||
|
(ev-api-check!
|
||||||
|
"actor is now booked into the whole series"
|
||||||
|
(len (ev/series-booked b store (quote nia) (quote yoga) ws we))
|
||||||
|
4)))
|
||||||
|
;; re-booking the series is idempotent
|
||||||
|
(ev-api-check!
|
||||||
|
"re-booking the series is idempotent"
|
||||||
|
(ev/series-count (ev/book-series! b store (quote nia) (quote yoga) ws we) :already)
|
||||||
|
4)
|
||||||
|
;; cancel the whole series
|
||||||
|
(let
|
||||||
|
((res (ev/cancel-series! b store (quote nia) (quote yoga) ws we)))
|
||||||
|
(do
|
||||||
|
(ev-api-check! "series cancel reports four cancellations" (ev/series-count res :cancelled) 4)
|
||||||
|
(ev-api-check!
|
||||||
|
"actor booked into nothing after series cancel"
|
||||||
|
(len (ev/series-booked b store (quote nia) (quote yoga) ws we))
|
||||||
|
0)))
|
||||||
|
;; capacity interacts per-occurrence: fill one occurrence first
|
||||||
|
(let
|
||||||
|
((b2 (persist/open))
|
||||||
|
(s2
|
||||||
|
(ev/schedule (ev/empty) (quote clinic) (ev-dt 2026 6 1 9 0) 30 {:freq :daily :count 3} 1)))
|
||||||
|
(do
|
||||||
|
(ev/book-occ! b2 s2 (quote x) (ev-occ (quote clinic) (ev-dt 2026 6 2 9 0) 30))
|
||||||
|
(let
|
||||||
|
((res (ev/book-series! b2 s2 (quote nia) (quote clinic) (ev-date 2026 6 1) (ev-date 2026 6 10))))
|
||||||
|
(do
|
||||||
|
(ev-api-check! "series booking succeeds on free occurrences" (ev/series-count res :booked) 2)
|
||||||
|
(ev-api-check! "series booking hits :full where capacity is taken" (ev/series-count res :full) 1)))))
|
||||||
|
;; unknown event id
|
||||||
|
(ev-api-check!
|
||||||
|
"series booking an unknown event yields no results"
|
||||||
|
(ev/book-series! b store (quote nia) (quote nope) ws we)
|
||||||
|
(list))))))
|
||||||
|
|
||||||
(define
|
(define
|
||||||
ev-api-tests-run!
|
ev-api-tests-run!
|
||||||
(fn
|
(fn
|
||||||
@@ -329,4 +388,5 @@
|
|||||||
(set! ev-api-failures (list))
|
(set! ev-api-failures (list))
|
||||||
(ev-api-run-all!)
|
(ev-api-run-all!)
|
||||||
(ev-api-cf-run-all!)
|
(ev-api-cf-run-all!)
|
||||||
|
(ev-api-sr-run-all!)
|
||||||
{:failures ev-api-failures :total (+ ev-api-pass ev-api-fail) :passed ev-api-pass :failed ev-api-fail})))
|
{:failures ev-api-failures :total (+ ev-api-pass ev-api-fail) :passed ev-api-pass :failed ev-api-fail})))
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ capacity rules, transactional booking, and a flow-driven notification dispatcher
|
|||||||
|
|
||||||
## Status (rolling)
|
## Status (rolling)
|
||||||
|
|
||||||
`bash lib/events/conformance.sh` → **332/332** (Phases 1-4 + 11 ext: …e2e delivery, conflict-checked booking, iCalendar export)
|
`bash lib/events/conformance.sh` → **341/341** (Phases 1-4 + 12 ext: …conflict-checked booking, iCalendar export, whole-series booking)
|
||||||
|
|
||||||
## Ground rules
|
## Ground rules
|
||||||
|
|
||||||
@@ -88,6 +88,13 @@ lib/events/api.sx ── (events/schedule) (events/book) (events/agenda) ──
|
|||||||
|
|
||||||
## Progress log
|
## Progress log
|
||||||
|
|
||||||
|
- 2026-06-07 — Whole-series booking (extension). `ev/book-series!` /
|
||||||
|
`ev/cancel-series!` apply a booking/cancel to every occurrence of one event
|
||||||
|
in a window (e.g. RSVP the whole weekly class), returning per-occurrence
|
||||||
|
(occ-key status) results; capacity is still enforced per occurrence (some
|
||||||
|
:booked, some :full). Idempotent re-book (all :already). `ev/series-count`
|
||||||
|
(tally a status), `ev/series-booked` (which occurrences the actor holds).
|
||||||
|
+9 tests, 341/341 green. This was the last flagged feature — surface saturated.
|
||||||
- 2026-06-07 — iCalendar (RFC 5545) export (extension). `ical.sx` serializes
|
- 2026-06-07 — iCalendar (RFC 5545) export (extension). `ical.sx` serializes
|
||||||
events to VEVENT / VCALENDAR text for import by standard clients. UTC
|
events to VEVENT / VCALENDAR text for import by standard clients. UTC
|
||||||
basic-format stamps (YYYYMMDDTHHMM00Z), DURATION (PT#H#M), and the full RRULE
|
basic-format stamps (YYYYMMDDTHHMM00Z), DURATION (PT#H#M), and the full RRULE
|
||||||
|
|||||||
Reference in New Issue
Block a user