Commit Graph
2 Commits
Author SHA1 Message Date
hamedandClaude Opus 5 b3c331f0cb perf(reports): read every resource's calendar in one batch, and close the owed tests
Writing the query-count test that task 14 owed showed the growth was real: one
resource cost 10 queries, six cost 33 — about five per resource, because the
available-minutes figure walked each resource's calendar on its own.

Holidays, tenant overrides and branch hours are identical for every resource in
a report, so they now load once outside the loop; shifts and exceptions load for
all resources in one query each. The batched path is a new method rather than a
change to rawAvailability, which the booking engine also calls. The test pins
the shape of the growth, not an exact count.

Also landed:

- app:segment:seed-templates with beauty, dental and physio presets. Building
  four segments and their requirements by hand is the first thing a new clinic
  must do and the most tedious; this gives them something to edit instead of an
  empty page. It refuses to touch a service that already has segments unless
  --force, and it will not invent resource types the tenant never defined.
- book-all is all-or-nothing, proven rather than asserted: with a calendar open
  one day a week and a 1-2 day protocol gap, session one finds a slot and
  session two cannot, and every session must come back planned.
- credit_refundable: false takes the credit back with a negative adjustment and
  deletes nothing — the ledger stays append-only.
- the segments editor has frontend tests, including that it sends back what the
  user sees and renders read-only without the permission.

useBranches now returns [] for a non-array payload instead of throwing
"branches.map is not a function" and taking the page down with it.

BookingLocationsScanTest built a Clinic around a Doctor loaded from a different
manager, which Doctrine treats as a new entity; it flushed fine most runs and
failed on cascade in others. It now loads the doctor from the same manager.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 14:22:22 +03:30
hamedandClaude Opus 5 3c43955800 feat(events): domain event outbox and the two reports that close the loop
Tasks 07 through 13 each changed something the rest of the system might want
to know about, with no contract for saying so. And task 05 shipped a powerful
segment editor with no feedback on whether a clinic defined its segments right.

Events
- A closed list of names, because a consumer branches on the string and a
  one-letter typo would produce an event nobody hears and no error either
- Payloads carry uuids and scalars only; non-scalars are dropped, not
  serialised, so a consumer always fetches fresh rather than reading a stale
  detached entity
- record() deliberately does not flush: the event row commits with the change
  it describes, so a rolled-back transaction leaves no event behind. A test
  pins exactly that
- app:events:publish drains the outbox; five failed attempts park a row with
  its error rather than deleting it, because a silently dropped event is a
  loss with no trace. app:events:prune only ever removes published rows

Reports
- Resource utilisation separates available, occupied and active minutes.
  The gap between occupied and active is what exposes a bad segment
  definition, and available is multiplied by capacity so a three-chair room
  does not read as permanently over 100%
- A resource with no calendar reports utilization: null, not zero — dividing
  by zero means something different from being idle
- Plan accuracy compares planned against actual duration per service and
  flags both directions: running short wastes capacity that could have been
  sold. Its row links straight to editing that service's segments, because a
  report with no route to a fix does not get read

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 12:27:54 +03:30