fix(tenancies-audit): resolve findings from 7-agent system-wide rename audit
MUST-FIX:
- src/app/api/v1/admin/users/[id]/permission-overrides/route.ts:70 — the
PUT allowlist still gated `reservations: {view,create,activate,cancel}`.
Stale: would reject valid `tenancies.{view,manage,cancel}` writes and
silently accept ghost `reservations.*` writes that never land. Replaced.
- src/lib/services/alert-rules.ts:68 — `reservation.no_agreement` alert
emitted `entityType: 'reservation'`. Every other tenancy-related
audit/socket/dashboard label is `'berth_tenancy'`. Inconsistent dedupe
+ activity-feed label miss.
- tests/e2e/exhaustive/08-portal.spec.ts:6 — hardcoded /portal/my-reservations
navigates to a 404 every run.
- tests/e2e/exhaustive/03-reservations.spec.ts — entire spec renamed to
03-tenancies.spec.ts; tab + button locators updated to match renamed UI.
SHOULD-FIX (consistency):
- src/components/clients/client-detail.tsx — useRealtimeInvalidation only
caught 3 of the 4 berth_tenancy:* events; added the `:created` listener.
- src/lib/services/client-merge.service.ts — MergeResult.movedRows.reservations
+ snapshot.reservations + local loserReservations / movedReservations
renamed to tenancies / loserTenancies / movedTenancies. No external
consumers grep-confirmed.
- src/lib/services/gdpr-bundle-builder.ts — GdprBundle.reservations field
renamed to .tenancies; user-facing HTML section "Reservations" → "Tenancies";
local reservationRows → tenancyRows.
- 6 UI copy strings: gdpr-export-button, bulk-archive-wizard,
bulk-hard-delete-dialog, hard-delete-dialog, admin-sections-browser ×2,
admin/import/page, won-status-panel — all "reservations" prose updated
to "tenancies" (occupancy-record sense).
- tests/integration/api/tenancies.test.ts — handler import aliases
`createReservationHandler` etc renamed to `createTenancyHandler` etc.
- tests/unit/services/berth-tenancies.test.ts — local helper makeReservation
→ makeTenancyLocal (avoids shadow of the renamed factory).
- scripts/audit-permissions.ts — stale allowlist entry for
/berth-reservations/[id]/route.ts removed (path no longer exists).
- docs/runbooks/permission-audit.md — stale row for same path removed.
- docs/tenancies-design.md — fixed factual error
("tenancies.service.ts" → "berth-tenancies.service.ts").
Verified: tsc clean, 1493/1493 vitest.
Dev-server note: the running `next dev` process started before P2 and
shows Turbopack cached compile errors against the renamed schema files.
Source is correct (./tenancies); restart `next dev` to clear the cache.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -189,7 +189,7 @@ const GROUPS: AdminGroup[] = [
|
||||
{
|
||||
href: 'custom-fields',
|
||||
label: 'Custom Fields',
|
||||
description: 'Tenant-defined fields for clients, yachts, and reservations.',
|
||||
description: 'Tenant-defined fields for clients, yachts, and tenancies.',
|
||||
icon: SlidersHorizontal,
|
||||
},
|
||||
{
|
||||
@@ -261,7 +261,7 @@ const GROUPS: AdminGroup[] = [
|
||||
{
|
||||
href: 'import',
|
||||
label: 'Bulk Import',
|
||||
description: 'CSV-driven imports for clients, yachts, and reservations.',
|
||||
description: 'CSV-driven imports for clients, yachts, and tenancies.',
|
||||
icon: FileUp,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -170,7 +170,7 @@ function BulkArchiveWizardBody({ open, onOpenChange, clientIds, onSuccess }: Pro
|
||||
|
||||
<div className="rounded-md border bg-muted/30 p-3 text-xs text-muted-foreground">
|
||||
Low-stakes defaults: release available/under-offer berths, keep sold ones, cancel
|
||||
reservations, leave invoices/signing requests alone. Yachts stay on the archived
|
||||
tenancies, leave invoices/signing requests alone. Yachts stay on the archived
|
||||
client. To customise per-client, archive that client individually instead.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -128,8 +128,8 @@ function BulkHardDeleteDialogBody({ onOpenChange, clientIds, onDeleted }: Props)
|
||||
</p>
|
||||
<div className="rounded-md border border-amber-300 bg-amber-50 p-3 text-amber-900 text-xs">
|
||||
For each client we delete: client record + addresses, contacts, notes, tags, portal
|
||||
user, GDPR records, all interests, all reservations. Signed documents, email threads,
|
||||
files and reminders are detached but kept.
|
||||
user, GDPR records, all interests, all tenancies. Signed documents, email threads, files
|
||||
and reminders are detached but kept.
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -114,6 +114,7 @@ export function ClientDetail({ clientId, currentUserId }: ClientDetailProps) {
|
||||
'yacht:ownership_transferred': [['clients', clientId]],
|
||||
'company_membership:added': [['clients', clientId]],
|
||||
'company_membership:ended': [['clients', clientId]],
|
||||
'berth_tenancy:created': [['clients', clientId]],
|
||||
'berth_tenancy:activated': [['clients', clientId]],
|
||||
'berth_tenancy:ended': [['clients', clientId]],
|
||||
'berth_tenancy:cancelled': [['clients', clientId]],
|
||||
|
||||
@@ -120,7 +120,7 @@ export function GdprExportButton({ clientId }: { clientId: string }) {
|
||||
<DialogTitle>Personal data export</DialogTitle>
|
||||
<DialogDescription>
|
||||
Bundles every record we hold about this client (profile, contacts, addresses, yachts,
|
||||
companies, interests, reservations, invoices, documents, audit log) into a ZIP with JSON
|
||||
companies, interests, tenancies, invoices, documents, audit log) into a ZIP with JSON
|
||||
and HTML copies. Used to satisfy GDPR Article 15 access requests.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
@@ -114,7 +114,7 @@ function HardDeleteDialogBody({ onOpenChange, clientId, clientName, onDeleted }:
|
||||
<ul className="mt-1.5 list-disc pl-5 text-xs space-y-0.5">
|
||||
<li>Client record + addresses, contacts, notes, tags</li>
|
||||
<li>Portal user account + GDPR consent records</li>
|
||||
<li>All pipeline interests + reservations for this client</li>
|
||||
<li>All pipeline interests + tenancies for this client</li>
|
||||
</ul>
|
||||
<p className="font-medium mt-2">What is preserved</p>
|
||||
<ul className="mt-1.5 list-disc pl-5 text-xs space-y-0.5">
|
||||
|
||||
@@ -100,9 +100,9 @@ export function WonStatusPanel({ interestId, outcome }: WonStatusPanelProps) {
|
||||
Won - wrap-up checklist
|
||||
</CardTitle>
|
||||
<p className="text-xs text-emerald-800/80">
|
||||
Upload anything that didn't flow through the system automatically. Reservations,
|
||||
deposit invoicing, and client billing are handled outside the CRM - this checklist is for
|
||||
the paperwork that lives on the deal itself.
|
||||
Upload anything that didn't flow through the system automatically. Tenancies, deposit
|
||||
invoicing, and client billing are handled outside the CRM - this checklist is for the
|
||||
paperwork that lives on the deal itself.
|
||||
</p>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2">
|
||||
|
||||
Reference in New Issue
Block a user