From 4075d48e7982c6d3afcc4548987bf6f2ef95ed33 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Thu, 12 Nov 2026 08:55:42 +0800 Subject: [PATCH 1/3] Include admitted-on-a-form people in Legal forms export, and refuse an ended This-shift request (D-155, D-156) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Josh ruled on 12 November 2026 (8:44–8:45 AM AWST) on the two review threads from PR #1623, which had already merged. Legal forms Print, Export and their disabled state now include the admitted-on-a-form group with the open-movement rows. A This-shift staff request whose chosen shift has already ended is refused, shown through the Staff needed sheet's existing save error; Next shift late-save behaviour is unchanged. decisions.md, FACTS.md and OWNER-RULINGS.md are left unchanged in this commit because they are signed out by open PR #1626. The D-155 and D-156 entries are in the pull request body, to be added to the log when that file is free. --- .../legal-forms/legal-forms-admitted.tsx | 9 +++ .../legal-forms/legal-forms-screen.tsx | 26 +++++-- .../staffing/staff-requests.ts | 5 ++ tests/ward-legal-form-admitted.test.ts | 36 ++++++++- .../ward-list-print-export-more.dom.test.tsx | 77 ++++++++++++++++++- tests/ward-staff-requests.test.ts | 33 +++++--- 6 files changed, 169 insertions(+), 17 deletions(-) diff --git a/src/components/ward-management/legal-forms/legal-forms-admitted.tsx b/src/components/ward-management/legal-forms/legal-forms-admitted.tsx index c873ed0f4a..9f6d364aa6 100644 --- a/src/components/ward-management/legal-forms/legal-forms-admitted.tsx +++ b/src/components/ward-management/legal-forms/legal-forms-admitted.tsx @@ -21,6 +21,15 @@ export function admittedOnAForm(movements: readonly Movement[], admissions: read .sort((a, b) => (a.closure?.at ?? a.openedAt) - (b.closure?.at ?? b.openedAt)); } +/** + * Everyone on a form on the list (D-155): the open-movement table rows, then the same + * admitted-on-a-form journeys the screen shows, with no duplicates. + */ +export function legalFormsListPopulation(rows: readonly M[], admitted: readonly M[]): M[] { + const seen = new Set(rows.map((row) => row.id)); + return [...rows, ...admitted.filter((movement) => !seen.has(movement.id))]; +} + /** What the record holds now: the form still to record, or the form recorded since arrival. */ export function admittedFormLine(movement: Movement): string { const progression = formProgression(movement); diff --git a/src/components/ward-management/legal-forms/legal-forms-screen.tsx b/src/components/ward-management/legal-forms/legal-forms-screen.tsx index ad56189003..8a3167baa7 100644 --- a/src/components/ward-management/legal-forms/legal-forms-screen.tsx +++ b/src/components/ward-management/legal-forms/legal-forms-screen.tsx @@ -84,7 +84,12 @@ import { legalFormGroupRows, isLegalDeadlineBreached, } from "./legal-forms-derivations"; -import { admittedOnAForm, LegalFormsAdmitted, WARD_RECORDS_ADMITTED } from "./legal-forms-admitted"; +import { + admittedOnAForm, + legalFormsListPopulation, + LegalFormsAdmitted, + WARD_RECORDS_ADMITTED, +} from "./legal-forms-admitted"; import { FocusPanel, typedReceipt } from "./legal-forms-panel"; import { AddTimeDialog } from "./legal-forms-add-time"; import { ChecklistView, FormRow, GAP_LABEL, PhoneFormRow, ROW_COLUMNS } from "./legal-form-rows"; @@ -254,6 +259,9 @@ function LegalFormsWorkspace({ initialMovementId, recordOnOpen = false }: Worksp const patientOf = (movement: Movement) => resolveSubjectPatient(movement, { patients, referrals }); const noDeadline = legalFormGroupRows(movements, now, "no-deadline"); const rows = [...withDeadline, ...noDeadline]; + // D-155: Print, Export and their disabled state use the same people as the on-screen list, + // including "Admitted on a form" (same `admitted` set the admitted group shows). + const listPopulation = legalFormsListPopulation(rows, admitted); const withoutForm = openMovements.length - rows.length; const breakdown = legalFormBreakdown(rows, now); const reminders = legalExpiryReminderSummary(movements, now, thresholds); @@ -545,14 +553,16 @@ function LegalFormsWorkspace({ initialMovementId, recordOnOpen = false }: Worksp if (!formCode) return; return raise("RECORD_LEGAL_FORM_DETAILS", { movementId: target.id, formCode, ...details }); }; - // Export (feature 44): everyone on a form, never names; legal times exactly as typed (D-93, D-102). + // Export (feature 44, D-155): everyone on a form on the list, including admitted on a form; + // never names; legal times exactly as typed (D-93, D-102). const exportList = async () => (await import("./legal-forms-export")).downloadLegalFormsList( - rows, + listPopulation, { umrnOf, edName: edNameOf, wardName: wardNameOf, now, dayZero }, { asAt: formatSheetMoment(now, dayZero, { includeYearAndZone: true }), - scope: "Legal forms; everyone on a form on the list, expiry typed first (highlights do not narrow it)", + scope: + "Legal forms; everyone on a form on the list, including admitted on a form, expiry typed first (highlights do not narrow it)", }, ); const addTimeTarget = rows.find((movement) => movement.id === addTimeId) ?? null; @@ -1112,8 +1122,12 @@ function LegalFormsWorkspace({ initialMovementId, recordOnOpen = false }: Worksp ) : null} {shownView === "history" ? null : ( <> - - + + )} diff --git a/src/components/ward-management/staffing/staff-requests.ts b/src/components/ward-management/staffing/staff-requests.ts index d1cd75543a..52666f44c4 100644 --- a/src/components/ward-management/staffing/staff-requests.ts +++ b/src/components/ward-management/staffing/staff-requests.ts @@ -172,6 +172,11 @@ export function reduceStaffRequest(state: WardFlowState, event: StaffRequestEven ) return reject(state, event, "REQUEST_WARD_STAFF needs one or more reasons chosen from the list"); const shiftStartAt = chosenShiftStart(event); + // D-156: a "This shift" pressed just before the shift ends, and saved just after, would + // otherwise keep the ended shift — accepted, immediately not open, and unable to be closed. + // "Next shift" across the same boundary stays valid and is kept. + if (event.shift === "this_shift" && event.now >= staffRequestEndsAt({ shiftStartAt })) + return reject(state, event, "This shift has already ended. Choose this shift or next shift again"); const ordered = STAFF_REQUEST_REASONS.filter((reason) => reasons.includes(reason)); // One open request per ward per shift: asking again updates it rather than adding a second. const existing = records.find( diff --git a/tests/ward-legal-form-admitted.test.ts b/tests/ward-legal-form-admitted.test.ts index c796c927a6..f1bc733b3c 100644 --- a/tests/ward-legal-form-admitted.test.ts +++ b/tests/ward-legal-form-admitted.test.ts @@ -10,7 +10,12 @@ import { import type { WardFlowRole } from "../src/components/ward-management/ward-flow-events"; import type { WardAccess } from "../src/components/ward-management/ward-role-access"; import { NOW_ANCHOR } from "../src/components/ward-management/ward-sites"; -import { admittedFormLine, admittedOnAForm } from "../src/components/ward-management/legal-forms/legal-forms-admitted"; +import { + admittedFormLine, + admittedOnAForm, + legalFormsListPopulation, +} from "../src/components/ward-management/legal-forms/legal-forms-admitted"; +import { isOpen } from "../src/components/ward-management/ward-derivations"; /** * Walk-through WT-03 (26 Oct 2026): a person who arrived on a Form 1A still showed Form 1A on the @@ -120,3 +125,32 @@ describe("the ward records the next form for a person admitted on a form (WT-03) expect(commandScopeRefusal(state, other, command, options)).toMatch(/outside your role's place/); }); }); + +describe("legal forms list population (D-155)", () => { + it("puts admitted-on-a-form journeys after the open-movement rows, without duplicates", () => { + const { state, movementId } = arrivedOnA1A(); + const rows = state.movements.filter((movement) => isOpen(movement) && movement.legalForm !== undefined); + const admitted = admittedOnAForm(state.movements, state.admissions); + expect(admitted.map((movement) => movement.id)).toContain(movementId); + expect(rows.map((movement) => movement.id)).not.toContain(movementId); + const population = legalFormsListPopulation(rows, admitted); + expect(population.map((movement) => movement.id)).toEqual([ + ...rows.map((movement) => movement.id), + ...admitted.map((movement) => movement.id), + ]); + expect(new Set(population.map((movement) => movement.id)).size).toBe(population.length); + // A journey already in the open rows is kept once, in the rows' place. + expect(legalFormsListPopulation(admitted, admitted).map((movement) => movement.id)).toEqual( + admitted.map((movement) => movement.id), + ); + }); + + it("is empty only when both the open-form rows and the admitted group are empty", () => { + expect(legalFormsListPopulation([], [])).toEqual([]); + const { state, movementId } = arrivedOnA1A(); + const admitted = admittedOnAForm(state.movements, state.admissions); + const onlyAdmitted = legalFormsListPopulation([], admitted); + expect(onlyAdmitted.map((movement) => movement.id)).toContain(movementId); + expect(onlyAdmitted).toHaveLength(admitted.length); + }); +}); diff --git a/tests/ward-list-print-export-more.dom.test.tsx b/tests/ward-list-print-export-more.dom.test.tsx index 3e5a13a4b7..1863197743 100644 --- a/tests/ward-list-print-export-more.dom.test.tsx +++ b/tests/ward-list-print-export-more.dom.test.tsx @@ -7,7 +7,9 @@ import { LegalFormsScreen } from "@/components/ward-management/legal-forms/legal import { MovementsScreen } from "@/components/ward-management/movements/movements-screen"; import { unitHref } from "@/components/ward-management/shell/ward-facade"; import { WardFlowOverride, WardFlowProvider, useWardFlow } from "@/components/ward-management/ward-flow-provider"; -import { seedWardFlowState } from "@/components/ward-management/ward-flow-reducer"; +import { admittedOnAForm } from "@/components/ward-management/legal-forms/legal-forms-admitted"; +import { isOpen } from "@/components/ward-management/ward-derivations"; +import { seedWardFlowState, type WardFlowState } from "@/components/ward-management/ward-flow-reducer"; import type { LeaveBed } from "@/components/ward-management/ward-model"; import { resolveSubjectPatient } from "@/components/ward-management/ward-patient-resolver"; import { NOW_ANCHOR } from "@/components/ward-management/ward-sites"; @@ -102,6 +104,39 @@ describe("Leave page", () => { }); }); +/** A person admitted on a Form 1A, still in an occupied bed (same shape as WT-03). */ +function arrivedOnA1A(clearOpenForms: boolean): { state: WardFlowState; movementId: string } { + const seed = seedWardFlowState(); + const stay = seed.admissions.find((admission) => admission.state === "occupied" && admission.patientId !== null)!; + const journey = seed.movements.find((movement) => !movement.closure)!; + const arrived = { + ...journey, + stage: "arrived" as const, + acceptedUnitId: stay.unitId, + admissionId: stay.id, + patientId: stay.patientId ?? undefined, + legalStatus: "Involuntary inpatient" as const, + legalForm: { code: "1A" as const, kind: "examination" as const }, + closure: { at: NOW_ANCHOR - 60, outcome: "arrived" as const, reason: "Patient arrived at the accepting unit" }, + }; + let movements = seed.movements.map((movement) => (movement.id === journey.id ? arrived : movement)); + if (clearOpenForms) { + movements = movements.map((movement) => + movement.id === journey.id || !isOpen(movement) ? movement : { ...movement, legalForm: undefined }, + ); + } + return { state: { ...seed, movements }, movementId: journey.id }; +} + +function FormsWith({ state }: { state: WardFlowState }) { + const flow = useWardFlow(); + return ( + + + + ); +} + describe("Legal forms Print and Export", () => { it("exports everyone on a form without names, each end exactly as typed", async () => { wrap(); @@ -118,6 +153,46 @@ describe("Legal forms Print and Export", () => { for (const cell of ends) expect(cell).not.toMatch(COUNT); expectNoNames(csv); }); + + it("includes a person admitted on a form in the export, and Print stays available (D-155)", async () => { + const { state, movementId } = arrivedOnA1A(false); + expect(admittedOnAForm(state.movements, state.admissions).map((movement) => movement.id)).toContain(movementId); + wrap(); + expect(screen.getByTestId("ward-legal-print")).not.toBeDisabled(); + expect(screen.getByTestId("ward-legal-export")).not.toBeDisabled(); + const csv = await exportedCsv("ward-legal-export"); + expect(csv).toContain(movementId); + expect(csv).toMatch(/including admitted on a form/); + expectNoNames(csv); + }); + + it("keeps Print and Export available when only admitted-on-a-form people are on the list (D-155)", async () => { + const { state, movementId } = arrivedOnA1A(true); + expect(state.movements.filter((movement) => isOpen(movement) && movement.legalForm !== undefined)).toHaveLength(0); + wrap(); + expect(screen.getByTestId("ward-legal-print")).not.toBeDisabled(); + expect(screen.getByTestId("ward-legal-export")).not.toBeDisabled(); + const csv = await exportedCsv("ward-legal-export"); + expect(csv).toContain(movementId); + const header = csv.split("\r\n").findIndex((line) => line.startsWith('"Record","UMRN","Form"')); + const data = csv + .split("\r\n") + .slice(header + 1) + .filter((line) => line.length > 0); + expect(data.some((line) => line.includes(movementId))).toBe(true); + expectNoNames(csv); + }); + + it("disables Print and Export when nobody is on a form", () => { + const seed = seedWardFlowState(); + const state = { + ...seed, + movements: seed.movements.map((movement) => ({ ...movement, legalForm: undefined })), + }; + wrap(); + expect(screen.getByTestId("ward-legal-print")).toBeDisabled(); + expect(screen.getByTestId("ward-legal-export")).toBeDisabled(); + }); }); describe("Movements Print and Export", () => { diff --git a/tests/ward-staff-requests.test.ts b/tests/ward-staff-requests.test.ts index e331ed37a0..2676194187 100644 --- a/tests/ward-staff-requests.test.ts +++ b/tests/ward-staff-requests.test.ts @@ -133,20 +133,35 @@ describe("staff needed requests", () => { expect(openStaffRequestFor(asked.staffRequests, UNIT, end)).toBeUndefined(); }); - it("keeps the shift the desk chose when the shared save lands just after the shift changes", () => { + it("keeps Next shift when the shared save lands just after the shift changes", () => { const boundary = shiftEndInstant(NOW_ANCHOR); const pressedAt = boundary - 1; const savedAt = boundary + 1; const startOf = (state: WardFlowState) => state.staffRequests!.filter((record) => record.unitId === UNIT).at(-1)!.shiftStartAt; - for (const shift of ["this_shift", "next_shift"] as const) { - const chosen = staffRequestShiftStart(shift, pressedAt); - const kept = wardFlowReducer(seed, ask({ shift, now: savedAt, shiftStartAt: chosen })); - expect(refused(seed, kept)).toBe(false); - expect(startOf(kept)).toBe(chosen); - // Without the desk's choice, the server's later clock reads the next shift (the old bug). - expect(startOf(wardFlowReducer(seed, ask({ shift, now: savedAt })))).toBe(staffRequestShiftStart(shift, savedAt)); - } + const chosen = staffRequestShiftStart("next_shift", pressedAt); + const kept = wardFlowReducer(seed, ask({ shift: "next_shift", now: savedAt, shiftStartAt: chosen })); + expect(refused(seed, kept)).toBe(false); + expect(startOf(kept)).toBe(chosen); + // Without the desk's choice, the server's later clock reads the following shift (the old bug). + expect(startOf(wardFlowReducer(seed, ask({ shift: "next_shift", now: savedAt })))).toBe( + staffRequestShiftStart("next_shift", savedAt), + ); + }); + + it("refuses This shift when the shared save lands after that shift has ended (D-156)", () => { + const boundary = shiftEndInstant(NOW_ANCHOR); + const pressedAt = boundary - 1; + const savedAt = boundary + 1; + const chosen = staffRequestShiftStart("this_shift", pressedAt); + const after = wardFlowReducer(seed, ask({ shift: "this_shift", now: savedAt, shiftStartAt: chosen })); + expect(refused(seed, after)).toBe(true); + expect(after.staffRequests?.filter((record) => record.unitId === UNIT) ?? []).toHaveLength(0); + expect(after.rejections.at(-1)?.reason).toBe("This shift has already ended. Choose this shift or next shift again"); + // A This shift that is still open at save is accepted, including a press just before the end. + const before = wardFlowReducer(seed, ask({ shift: "this_shift", now: pressedAt, shiftStartAt: chosen })); + expect(refused(seed, before)).toBe(false); + expect(before.staffRequests!.filter((record) => record.unitId === UNIT).at(-1)!.shiftStartAt).toBe(chosen); expect(staffRequestShiftStart("this_shift", pressedAt)).toBe(currentShiftStartInstant(NOW_ANCHOR)); }); From b425e71941f5ce657187016f9d38077712e4443a Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Thu, 12 Nov 2026 09:03:56 +0800 Subject: [PATCH 2/3] Refresh journey source-review digests after D-155 and D-156 The Legal forms screen and staff-request reducer hashes are updated after comparing behaviour. RECORD_LEGAL_FORM_* and CLOSE_WARD_STAFF_REQUEST are unchanged in effect. REQUEST_WARD_STAFF's model now records that a This-shift request whose chosen shift has already ended is refused (D-156). --- docs/ward-flow/journey/MAP-INVENTORY.md | 7 ++++--- docs/ward-flow/journey/actions.json | 12 ++++++------ docs/ward-flow/journey/event-dispositions.json | 11 ++++++----- docs/ward-flow/journey/machines.json | 2 +- docs/ward-flow/journey/ward-journey-explorer.html | 2 +- 5 files changed, 18 insertions(+), 16 deletions(-) diff --git a/docs/ward-flow/journey/MAP-INVENTORY.md b/docs/ward-flow/journey/MAP-INVENTORY.md index 93d97ca9b5..9fbd377474 100644 --- a/docs/ward-flow/journey/MAP-INVENTORY.md +++ b/docs/ward-flow/journey/MAP-INVENTORY.md @@ -1690,18 +1690,19 @@ Source-contract SHA256: 7cfda9a71c556b4d10a573977f0ef64456a4f9bb9890c7a236952cd7 Event/dispatch reference: src/components/ward-management/ward-flow-reducer.ts:2266 Subject: A ward's staff request for this shift or next shift Before: Known ward; no open request for that ward and shift, or one to update - After: staffRequests holds one open request for the ward and shift with count and ordered reasons + After: staffRequests holds one open request for the ward and shift with count and ordered reasons, or the event is refused when This shift has already ended Scope: Ward, coordinator or site bed manager in EVENT_ROLE; a ward acts only for its own ward (actingUnitId must equal unitId). Executive changes nothing (D-80). Shared authenticated command scope additionally checks the permitted place and stored record; EVENT_ROLE alone is not place authority. payload: unitId; shift this or next; count 1, 2, 3 or 4 or more payload: reasons: one or more distinct items from STAFF_REQUEST_REASONS; actingUnitId for ward callers; role and now payload: optional shiftStartAt: the start of the shift the person chose; kept only when it is that shift's start at now or 5 minutes earlier, otherwise the shift at now is used guards: Known unit; shift and count from their lists guards: Reject empty, duplicate or unlisted reasons, and a ward naming another ward + guards: Reject a This-shift request whose chosen shift has already ended (D-156); a Next-shift request that lands just after the shift changes is still kept effects: Appends a record, or updates count, reasons and updatedAt on the open one; never names a patient effects: Does not change beds, placement, movements or any patient stage tests: tests/ward-staff-requests.test.ts Reviewed source: src/components/ward-management/ward-flow-reducer.ts:2268; SHA256 ff62a01c308a19a3cd2f6ea39a1502194855812ad15c7599a441bc8c9e32c4f6 - Reviewed source: src/components/ward-management/staffing/staff-requests.ts:156; SHA256 e47b9fadc1f70f84b56bd4e91eb36f5b95f85da6234e10f6251bea10c3f6237a + Reviewed source: src/components/ward-management/staffing/staff-requests.ts:156; SHA256 9b8adc8255527c0e8dbf37b683884c7b76af12f5b7b7c841a07e6a189f6206f8 Reviewed source: src/components/ward-management/ward-flow-event-roles.ts:605; SHA256 808d614bd517bb11c0ef517ce5632dd3caba8a71758476401f35da82fe26afda - CLOSE_WARD_STAFF_REQUEST — modelled; event roles: ward, coordinator, bed_manager Source-reviewed command model: an open staff request closed as arranged, not available or withdrawn. @@ -1719,7 +1720,7 @@ Source-contract SHA256: 7cfda9a71c556b4d10a573977f0ef64456a4f9bb9890c7a236952cd7 effects: Does not change beds, placement, movements or any patient stage tests: tests/ward-staff-requests.test.ts Reviewed source: src/components/ward-management/ward-flow-reducer.ts:2269; SHA256 ff62a01c308a19a3cd2f6ea39a1502194855812ad15c7599a441bc8c9e32c4f6 - Reviewed source: src/components/ward-management/staffing/staff-requests.ts:195; SHA256 e47b9fadc1f70f84b56bd4e91eb36f5b95f85da6234e10f6251bea10c3f6237a + Reviewed source: src/components/ward-management/staffing/staff-requests.ts:200; SHA256 9b8adc8255527c0e8dbf37b683884c7b76af12f5b7b7c841a07e6a189f6206f8 Reviewed source: src/components/ward-management/ward-flow-event-roles.ts:615; SHA256 808d614bd517bb11c0ef517ce5632dd3caba8a71758476401f35da82fe26afda - SET_SITE_FEATURE_SWITCH — modelled; event roles: coordinator Source-reviewed command model: one listed feature switched off, or back on, at one site (Batch 4 item 20, D-135; access admin by D-142). diff --git a/docs/ward-flow/journey/actions.json b/docs/ward-flow/journey/actions.json index 5c50823142..793c9416a2 100644 --- a/docs/ward-flow/journey/actions.json +++ b/docs/ward-flow/journey/actions.json @@ -3093,8 +3093,8 @@ }, { "path": "src/components/ward-management/legal-forms/legal-forms-screen.tsx", - "line": 365, - "sha256": "21e9f1961cac15e3deac106f62f32fd6f5b9dccd51020fd7827ebb52043b9d19" + "line": 373, + "sha256": "de3fda91e7efd4a5d2431400fc59468f647a9fcf6a68d8576ea0643d5886dd1d" }, { "path": "src/components/ward-management/ward-flow-record-events.ts", @@ -3239,8 +3239,8 @@ }, { "path": "src/components/ward-management/legal-forms/legal-forms-screen.tsx", - "line": 1303, - "sha256": "21e9f1961cac15e3deac106f62f32fd6f5b9dccd51020fd7827ebb52043b9d19" + "line": 1318, + "sha256": "de3fda91e7efd4a5d2431400fc59468f647a9fcf6a68d8576ea0643d5886dd1d" }, { "path": "src/components/ward-management/ward-flow-record-legal-events.ts", @@ -6238,8 +6238,8 @@ }, { "path": "src/components/ward-management/legal-forms/legal-forms-screen.tsx", - "line": 531, - "sha256": "21e9f1961cac15e3deac106f62f32fd6f5b9dccd51020fd7827ebb52043b9d19" + "line": 539, + "sha256": "de3fda91e7efd4a5d2431400fc59468f647a9fcf6a68d8576ea0643d5886dd1d" }, { "path": "src/components/ward-management/ward-flow-record-legal-events.ts", diff --git a/docs/ward-flow/journey/event-dispositions.json b/docs/ward-flow/journey/event-dispositions.json index 82c7fb6f6d..e0479ceea7 100644 --- a/docs/ward-flow/journey/event-dispositions.json +++ b/docs/ward-flow/journey/event-dispositions.json @@ -4176,7 +4176,7 @@ "model": { "subject": "A ward's staff request for this shift or next shift", "before": "Known ward; no open request for that ward and shift, or one to update", - "after": "staffRequests holds one open request for the ward and shift with count and ordered reasons", + "after": "staffRequests holds one open request for the ward and shift with count and ordered reasons, or the event is refused when This shift has already ended", "payload": [ "unitId; shift this or next; count 1, 2, 3 or 4 or more", "reasons: one or more distinct items from STAFF_REQUEST_REASONS; actingUnitId for ward callers; role and now", @@ -4185,7 +4185,8 @@ "scope": "Ward, coordinator or site bed manager in EVENT_ROLE; a ward acts only for its own ward (actingUnitId must equal unitId). Executive changes nothing (D-80). Shared authenticated command scope additionally checks the permitted place and stored record; EVENT_ROLE alone is not place authority.", "guards": [ "Known unit; shift and count from their lists", - "Reject empty, duplicate or unlisted reasons, and a ward naming another ward" + "Reject empty, duplicate or unlisted reasons, and a ward naming another ward", + "Reject a This-shift request whose chosen shift has already ended (D-156); a Next-shift request that lands just after the shift changes is still kept" ], "effects": [ "Appends a record, or updates count, reasons and updatedAt on the open one; never names a patient", @@ -4200,7 +4201,7 @@ { "path": "src/components/ward-management/staffing/staff-requests.ts", "line": 156, - "sha256": "e47b9fadc1f70f84b56bd4e91eb36f5b95f85da6234e10f6251bea10c3f6237a" + "sha256": "9b8adc8255527c0e8dbf37b683884c7b76af12f5b7b7c841a07e6a189f6206f8" }, { "path": "src/components/ward-management/ward-flow-event-roles.ts", @@ -4241,8 +4242,8 @@ }, { "path": "src/components/ward-management/staffing/staff-requests.ts", - "line": 195, - "sha256": "e47b9fadc1f70f84b56bd4e91eb36f5b95f85da6234e10f6251bea10c3f6237a" + "line": 200, + "sha256": "9b8adc8255527c0e8dbf37b683884c7b76af12f5b7b7c841a07e6a189f6206f8" }, { "path": "src/components/ward-management/ward-flow-event-roles.ts", diff --git a/docs/ward-flow/journey/machines.json b/docs/ward-flow/journey/machines.json index cb73aa6b7d..6d9fdabc40 100644 --- a/docs/ward-flow/journey/machines.json +++ b/docs/ward-flow/journey/machines.json @@ -1744,7 +1744,7 @@ { "path": "src/components/ward-management/legal-forms/legal-forms-screen.tsx", "line": 1, - "sha256": "21e9f1961cac15e3deac106f62f32fd6f5b9dccd51020fd7827ebb52043b9d19" + "sha256": "de3fda91e7efd4a5d2431400fc59468f647a9fcf6a68d8576ea0643d5886dd1d" }, { "path": "src/components/ward-management/legal-forms/legal-forms-sheets.tsx", diff --git a/docs/ward-flow/journey/ward-journey-explorer.html b/docs/ward-flow/journey/ward-journey-explorer.html index 446d353c83..a06baaef2f 100644 --- a/docs/ward-flow/journey/ward-journey-explorer.html +++ b/docs/ward-flow/journey/ward-journey-explorer.html @@ -1284,7 +1284,7 @@

What to do here