Files
julian a9ccf9e20c feat(carwash): Car Wash v1 + per-till shifts + site-level pay-at + till access by module permission
Car Wash — the pilot venue module (wiki/decisions/venue-modules.md):
- Master data (categories × services price matrix) at /setup/carwash; the desk at /wash
  (ticket lookup → order; open queue oldest-first: Done / Paid cash / Paid card / Void;
  Finished list). Orders freeze names + price; their life is signed (carwash_order,
  carwash_payment). Migration 0027.
- Where money is taken is a SITE setting (carwash_config.pay_at, migration 0028, signed
  config_change on a flip) — no per-order radio; a stale client is refused (409).
- Core seams: PayStation charge providers (a booth-paid wash rides the parking payment as
  chargeLines) + applyValidation() shared with the merchant route. A bay-paid, done wash
  signs the $0 parking payment so the exit reader releases the car.
- "Parking discount" modes for the wash: free while the wash runs (+ tolerance) and wash
  price off the fee (floored at 0), resolved at done and anchored at the order's intake
  (the entry-anchored version comped a 74-day stay); typed-amount and percent hidden for
  the wash. Long durations render y/d/h/m.

Tills — a shift belongs to a till, not the site (wiki/concepts/shift.md §Tills):
- TillId booth|carwash; every money event names its till (absent = booth, so the chain
  re-folds identically). ShiftService is per till: single-open, folds, X/Z-reports,
  vouchers, carry-forward. A bay payment needs the carwash shift.
- Working a till needs that till's module permission (manifest tillPermission; 403
  till_forbidden); /api/shift/tills lists only the role's tills.
- Web: ShiftButton per till (header = booth, wash desk = carwash); shift hub lists every
  open shift with till badges + filter; drawer hub switches tills.

Modules: landing per module (index route resolves booth → module landing → shifts →
profile); guards bounce to "/", /booth needs session:read.

Tests: carwash e2e suite (settings, intake, booth/bay paths, modes, void, gate, pay-at
policy, till permissions), 6 per-till shift tests; suite green (1 pre-existing flaky
backup test under the parallel run).

Claude-Session: https://claude.ai/code/session_01FWncR69HgGPuei1dLrW3cU
2026-09-05 13:23:09 +02:00

59 lines
2.2 KiB
SQL
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
-- Car Wash module (wiki/decisions/venue-modules.md): admin master data (categories,
-- services, the category × service price matrix) + the order rows that are the wash
-- desk's queue. Orders freeze names + price at intake; their life is signed onto the
-- ledger (carwash_order / carwash_payment). Additive; tables exist whether or not the
-- module is entitled/activated at a site (modules are always migrated).
CREATE TABLE `carwash_categories` (
`id` text PRIMARY KEY NOT NULL,
`name` text NOT NULL,
`sort_order` integer DEFAULT 0 NOT NULL,
`active` integer DEFAULT true NOT NULL,
`created_at` text DEFAULT (current_timestamp) NOT NULL,
`deleted_at` text,
`deleted_by` text
);--> statement-breakpoint
CREATE TABLE `carwash_services` (
`id` text PRIMARY KEY NOT NULL,
`name` text NOT NULL,
`sort_order` integer DEFAULT 0 NOT NULL,
`active` integer DEFAULT true NOT NULL,
`created_at` text DEFAULT (current_timestamp) NOT NULL,
`deleted_at` text,
`deleted_by` text
);--> statement-breakpoint
CREATE TABLE `carwash_prices` (
`category_id` text NOT NULL,
`service_id` text NOT NULL,
`price_minor` integer NOT NULL,
PRIMARY KEY(`category_id`, `service_id`),
FOREIGN KEY (`category_id`) REFERENCES `carwash_categories`(`id`) ON UPDATE no action ON DELETE no action,
FOREIGN KEY (`service_id`) REFERENCES `carwash_services`(`id`) ON UPDATE no action ON DELETE no action
);--> statement-breakpoint
CREATE TABLE `carwash_orders` (
`id` text PRIMARY KEY NOT NULL,
`identity` text NOT NULL,
`plate` text,
`category_id` text NOT NULL,
`category_name` text NOT NULL,
`service_id` text NOT NULL,
`service_name` text NOT NULL,
`price_minor` integer NOT NULL,
`currency` text NOT NULL,
`pay_at` text NOT NULL,
`status` text DEFAULT 'open' NOT NULL,
`created_at` text NOT NULL,
`created_by` text NOT NULL,
`done_at` text,
`done_by` text,
`paid_at` text,
`paid_by` text,
`tender` text,
`payment_event_id` text,
`validation_event_id` text,
`void_at` text,
`void_by` text,
`void_reason` text
);--> statement-breakpoint
CREATE INDEX `carwash_orders_identity_idx` ON `carwash_orders` (`identity`);--> statement-breakpoint
CREATE INDEX `carwash_orders_status_idx` ON `carwash_orders` (`status`);