feat(tenancies-p1): module-enabled gate + admin toggle endpoints

Part of the locked Tenancies module design (docs/tenancies-design.md).
This PR is the gating infrastructure — the actual table rename
(berth_reservations -> tenancies) + self-FKs + perm-rename + sidebar
entry land in subsequent PRs.

What ships:
- `system_settings.tenancies_module_enabled` registry entry (port-scoped
  boolean, default false). Surfaces in the registry-driven admin form
  + the resolveForAdminAPI chain.
- `src/lib/services/tenancies-module.service.ts` with:
  * isTenanciesModuleEnabled(portId) — checks the admin setting AND
    the lazy "any berth_reservations row exists" sentinel
  * enableTenanciesModule / disableTenanciesModule — idempotent
    upserts on the system_settings row
  * assertTenanciesModuleEnabled — throw-on-disabled helper for
    route handlers (NotFoundError -> 404)
- Three admin endpoints under /api/v1/admin/tenancies-module/
  (status / enable / disable), all gated on admin.manage_settings.

Behaviour today: with the module off (default), nothing changes.
Sidebar, entity tabs, top-level page, webhook auto-create branch,
and dashboard widgets all continue to read the same flag and stay
hidden until either an admin toggles it ON or the first auto-create
flips it via the lazy "row exists" sentinel.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-25 03:58:19 +02:00
parent c7dbe0bb10
commit d3ec9fdb4e
5 changed files with 191 additions and 0 deletions

View File

@@ -600,6 +600,24 @@ export const REGISTRY: SettingEntry[] = [
placeholder: 'Cold',
},
// ─── Operations - Tenancies module ────────────────────────────────────────
// Platform-wide gate for the Tenancies (occupancy-record) surface area.
// Disabled by default. A first row INSERT on the `tenancies` table flips
// this on automatically (`pg_advisory_xact_lock` per port keeps the flip
// race-safe). Admins can also enable explicitly from Admin -> Operations,
// and disabling with existing rows is a soft hide (data is preserved but
// invisible until re-enabled).
{
key: 'tenancies_module_enabled',
section: 'operations.tenancies',
label: 'Tenancies module',
description:
'When enabled, the platform tracks who occupies each berth (Tenancies). Without it, sold berths stay sold but the platform does not model the occupancy record. Auto-enables on the first tenancy created (e.g. via a signed Reservation Agreement).',
type: 'boolean',
scope: 'port',
defaultValue: false,
},
// ─── Residential - partner forwarding ──────────────────────────────────────
{
key: 'residential_partner_recipients',