feat(schema): berths.archived_at + clients.source_inquiry_id + email_bounces
Step 3 schema additions per PRE-DEPLOY-PLAN § 1.4. berths.archived_at (+ archived_by, archive_reason) — soft-delete column so retired moorings can be hidden from the public feed and admin lists without losing historical interest joins. Partial index `idx_berths_active` on (port_id) WHERE archived_at IS NULL keeps the active-only list path fast. Already wired: - /api/public/berths and /api/public/berths/[mooringNumber] now filter out archived rows. - berths.service.listBerths defaults to active-only with an ?includeArchived=true escape hatch for the archive bin. clients.source_inquiry_id — text column with ON DELETE SET NULL FK to website_submissions(id). Preserves the linkage from a website inquiry to the client that came out of the "Convert to client" triage flow (P-4.5). Drives the conversion-funnel-by-source chart (Step 6). The Drizzle column ships without `.references()` to avoid the cross-file circular import; the FK lives in the migration SQL. email_bounces table — bounce-monitoring storage. The DSN poller worker (forthcoming, depends on this table existing) writes one row per parsed bounce; consumers join via (original_send_type, original_send_id). Three secondary indexes cover the expected access patterns (port + recent bounces; lookup by bounced address; lookup by original send). Schema additions plus the migration SQL are ready for `pnpm db:push` (or the migration runner once its journal is backfilled — separate concern, journal currently stops at 0042 despite migrations through 0065 existing on disk). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { sql } from 'drizzle-orm';
|
||||
import {
|
||||
pgTable,
|
||||
text,
|
||||
@@ -77,6 +78,13 @@ export const berths = pgTable(
|
||||
statusLastChangedBy: text('status_last_changed_by'), // user ID
|
||||
statusLastChangedReason: text('status_last_changed_reason'),
|
||||
statusLastModified: timestamp('status_last_modified', { withTimezone: true }),
|
||||
// Soft-delete: when set, the berth is hidden from the public feed
|
||||
// (`/api/public/berths`) and admin lists by default. Sticks around in
|
||||
// historical interest joins so reporting against pre-archive deals
|
||||
// still works. Hard-delete is reserved for genuine data corruption.
|
||||
archivedAt: timestamp('archived_at', { withTimezone: true }),
|
||||
archivedBy: text('archived_by'),
|
||||
archiveReason: text('archive_reason'),
|
||||
// Optional override flag carried over from NocoDB ("auto" or null in legacy data).
|
||||
// Reserved for future "manual override" semantics; not surfaced in the UI today.
|
||||
statusOverrideMode: text('status_override_mode'),
|
||||
@@ -97,6 +105,9 @@ export const berths = pgTable(
|
||||
index('idx_berths_status').on(table.portId, table.status),
|
||||
index('idx_berths_area').on(table.portId, table.area),
|
||||
uniqueIndex('idx_berths_mooring').on(table.portId, table.mooringNumber),
|
||||
index('idx_berths_active')
|
||||
.on(table.portId)
|
||||
.where(sql`${table.archivedAt} IS NULL`),
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user