mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 21:32:18 +00:00
fix(vaccinations): keep the booster mint inside the sections the grant opens
`POST /api/vaccinations/{id}/booster` is declared `("write", "profile")`, and
that is right for the act — filing the Impfpass and arming the booster it
suggests are one thing to the person doing them. The row it writes is not: a
`MeasurementReminder` belongs to the measurements section, every direct route
over the model declares it there, and creating one needs MANAGE. So a WRITE
grant scoped to the health background alone produced a reminder on the owner's
checkup list that the delegate could neither read nor change through the
section that owns it.
The seam is fenced the way the visits service fences a checkup closure, with
one difference stated at the call site: this refuses rather than skips. There
the closure is one effect of a larger save and reporting it as not done keeps
the save honest; here the reminder is the whole request, and a 201 over a row
that was never written would tell the person their booster is planned while
nothing rings. The prompt that raises the call asks the same question client-
side, so a delegate outside the section is not offered it in the first place;
the dose itself still logs either way.
`vaccination-booster-scoped-delegate.test.ts` drives the owner, a delegate
holding both sections, a profile-only delegate and a whole-record one. The
scoped-refusal leg fails against the previous handler, which answered 201 and
left the row.
This commit is contained in:
1 parent
0ee26625be
commit
1b74bf8b54
5 files changed
+231
-7
No files matched your search
@@ -37,6 +37,10 @@ export function VaccinationsView() {
|
||||
// next door.
|
||||
const canAddDose = canWriteDomain("profile");
|
||||
const canManageProfile = canManageDomain("profile");
|
||||
// Planning the booster mints a `MeasurementReminder`, which lives in the
|
||||
// measurements section rather than this one. A grant that opened only the
|
||||
// health background is refused there, so the offer is not made.
|
||||
const canPlanBooster = canWriteDomain("measurements");
|
||||
const { data, isLoading, isError, refetch } = useVaccinations();
|
||||
const records = data?.vaccinations ?? [];
|
||||
|
||||
@@ -91,7 +95,8 @@ export function VaccinationsView() {
|
||||
onCreated={(created) => {
|
||||
// Offer the booster only when the catalogue entry carries an
|
||||
// interval; a free-text or one-off dose never prompts.
|
||||
if (boosterOfferFor(created)) setBoosterFor(created);
|
||||
if (canPlanBooster && boosterOfferFor(created))
|
||||
setBoosterFor(created);
|
||||
}}
|
||||
/>
|
||||
|
||||
|
||||
Reference in new issue
Block a user