diff --git a/specs/owners.json b/specs/owners.json index 52909bc31f..867c552498 100644 --- a/specs/owners.json +++ b/specs/owners.json @@ -1,3 +1,3 @@ { - "owners": ["attendees", "payments"] + "owners": ["attendees", "payments", "servicing"] } diff --git a/specs/servicing/hold-and-cost.feature b/specs/servicing/hold-and-cost.feature new file mode 100644 index 0000000000..a34d6390dd --- /dev/null +++ b/specs/servicing/hold-and-cost.feature @@ -0,0 +1,48 @@ +@story:servicing.hold-and-cost +@owner:servicing @risk:medium +@actor:organiser +@edition:managed @edition:self-hosted +Feature: An organiser holds and costs a service event + A service event reserves listing capacity without creating a customer ticket. + The organiser can see, duplicate, delete, and cost the hold. + + @rule:servicing.hold-is-visible-and-private + Rule: A service hold appears to the organiser but not to the customer + The hold reserves capacity and never becomes a public listing or ticket. + + @case:servicing.hold-on-dashboard + Scenario: The organiser sees a new hold on the dashboard + Given an organiser has created a Boiler Service hold on Room A + Then the admin dashboard shows the Boiler Service hold + And the public site does not show Boiler Service + + @rule:servicing.hold-can-be-duplicated + Rule: A duplicated hold is an independent copy + Duplicating a service event creates a second event with the same bookings. + + @case:servicing.duplicate-hold + Scenario: The organiser duplicates an annual inspection + Given an organiser has created an Annual Inspection hold on Annual Room + When the organiser duplicates the service event + Then the admin dashboard shows two Annual Inspection holds + + @rule:servicing.hold-can-be-deleted + Rule: Deleting a hold frees the reserved capacity + The hold and its booking rows are removed. + + @case:servicing.delete-hold + Scenario: The organiser deletes a Boiler Service hold + Given an organiser has created a Boiler Service hold on Room A + When the organiser deletes the service event + Then the admin dashboard no longer shows Boiler Service + And the held listing has its full capacity restored + + @rule:servicing.hold-can-be-costed + Rule: The organiser can record a cost against a hold + A cost is money spent on the service, recorded against one of the held listings. + + @case:servicing.record-cost + Scenario: The organiser records a cost for a Boiler Service + Given an organiser has created a Boiler Service hold on Room A + When the organiser records a cost of 90.00 for Boiler Service + Then the service event page shows the recorded cost diff --git a/test/e2e/servicing/e2e-narrative.test.ts b/test/e2e/servicing/e2e-narrative.test.ts deleted file mode 100644 index e0ed7107df..0000000000 --- a/test/e2e/servicing/e2e-narrative.test.ts +++ /dev/null @@ -1,137 +0,0 @@ -/** - * Servicing §21 — end-to-end narrative scenarios. - * - * Story-driven e2e flows (TestBrowser) that string the units together the way - * a real operator would hit them — each is one coherent narrative, not a - * grab-bag of assertions. They reuse the same fixtures the [I] tests build. - * - * Implementation contract (test-first): - * - Servicing routes: `GET/POST /admin/servicing/new` (create), - * `GET/POST /admin/servicing/:id` (edit), `POST /admin/servicing/:id/delete`, - * `POST /admin/servicing/:id/duplicate`. Form fields mirror the attendee - * form minus the contact/payment fields (§0/§6). - */ -// jscpd:ignore-start -import { expect } from "@std/expect"; -import { describe, it as test } from "@std/testing/bdd"; -import { useE2eBrowser } from "#test-utils/e2e.ts"; -import { - findServicingLink, - setupBrowserWithHold, -} from "#test-utils/servicing.ts"; -import { enablePublicSite } from "#test-utils/settings.ts"; - -// jscpd:ignore-end - -const ROOM_A = { max_attendees: "5", name: "Room A" } as const; -const DAILY_ROOM_A = { - duration_days: "1", - listing_type: "daily", - max_attendees: "5", - max_quantity: "5", - maximum_days_after: "365", - name: "Room A", -} as const; - -const costListingIdFromForm = (html: string): string => { - const match = html.match( - /