Files
pn-new-crm/src/lib/db/schema/relations.ts
Matt Ciaccio 6e3d910c76 refactor(interests): migrate callers to interest_berths junction + drop berth_id
Phase 2b of the berth-recommender refactor (plan §3.4). Every caller of
the legacy `interests.berth_id` column now reads / writes through the
`interest_berths` junction via the helper service introduced in Phase 2a;
the column itself is dropped in a final migration.

Service-layer changes
- interests.service: filter `?berthId=X` becomes EXISTS-against-junction;
  list enrichment uses `getPrimaryBerthsForInterests`; create/update/
  linkBerth/unlinkBerth all dispatch through the junction helpers, with
  createInterest's row insert + junction write sharing a single transaction.
- clients / dashboard / report-generators / search: leftJoin chains pivot
  through `interest_berths` filtered by `is_primary=true`.
- eoi-context / document-templates / berth-rules-engine / portal /
  record-export / queue worker: read primary via `getPrimaryBerth(...)`.
- interest-scoring: berthLinked is now derived from any junction row count.
- dedup/migration-apply + public interest route: write a primary junction
  row alongside the interest insert when a berth is provided.

API contract preserved: list/detail responses still emit `berthId` and
`berthMooringNumber`, derived from the primary junction row, so frontend
consumers (interest-form, interest-detail-header) need no changes.

Schema + migration
- Drop `interestsRelations.berth` and `idx_interests_berth`.
- Replace `berthsRelations.interests` with `interestBerths`.
- Migration 0029_puzzling_romulus drops `interests.berth_id` + the index.
- Tests that previously inserted `interests.berthId` now seed a primary
  junction row alongside the interest.

Verified: vitest 995 passing (1 unrelated pre-existing flake in
maintenance-cleanup.test.ts), tsc clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 02:41:52 +02:00

872 lines
25 KiB
TypeScript

import { relations } from 'drizzle-orm';
// Ports
import { ports } from './ports';
// Users
import { userProfiles, roles, portRoleOverrides, userPortRoles } from './users';
// Clients
import {
clients,
clientContacts,
clientRelationships,
clientNotes,
clientTags,
clientMergeLog,
clientAddresses,
} from './clients';
// Interests
import { interests, interestNotes, interestTags, interestBerths } from './interests';
// Yachts
import { yachts, yachtOwnershipHistory, yachtNotes, yachtTags } from './yachts';
// Companies
import {
companies,
companyMemberships,
companyAddresses,
companyNotes,
companyTags,
} from './companies';
// Berths
import {
berths,
berthMapData,
berthRecommendations,
berthWaitingList,
berthMaintenanceLog,
berthTags,
} from './berths';
// Reservations
import { berthReservations } from './reservations';
// Documents
import {
files,
documents,
documentSigners,
documentEvents,
documentTemplates,
documentWatchers,
formTemplates,
formSubmissions,
} from './documents';
// Financial
import { expenses, invoices, invoiceLineItems, invoiceExpenses } from './financial';
// Email
import { emailAccounts, emailThreads, emailMessages } from './email';
// Operations
import {
reminders,
googleCalendarCache,
googleCalendarTokens,
notifications,
scheduledReports,
reportRecipients,
generatedReports,
} from './operations';
// System
import {
auditLogs,
tags,
webhooks,
webhookDeliveries,
systemSettings,
savedViews,
scratchpadNotes,
userNotificationPreferences,
customFieldDefinitions,
customFieldValues,
} from './system';
import { residentialClients, residentialInterests } from './residential';
// ─── Ports ────────────────────────────────────────────────────────────────────
export const portsRelations = relations(ports, ({ many }) => ({
userPortRoles: many(userPortRoles),
portRoleOverrides: many(portRoleOverrides),
clients: many(clients),
interests: many(interests),
yachts: many(yachts),
companies: many(companies),
berths: many(berths),
berthReservations: many(berthReservations),
documents: many(documents),
documentTemplates: many(documentTemplates),
formTemplates: many(formTemplates),
expenses: many(expenses),
invoices: many(invoices),
emailAccounts: many(emailAccounts),
emailThreads: many(emailThreads),
reminders: many(reminders),
notifications: many(notifications),
scheduledReports: many(scheduledReports),
auditLogs: many(auditLogs),
tags: many(tags),
files: many(files),
webhooks: many(webhooks),
systemSettings: many(systemSettings),
savedViews: many(savedViews),
userNotificationPreferences: many(userNotificationPreferences),
customFieldDefinitions: many(customFieldDefinitions),
residentialClients: many(residentialClients),
residentialInterests: many(residentialInterests),
berthMaintenanceLogs: many(berthMaintenanceLog),
clientMergeLogs: many(clientMergeLog),
clientRelationships: many(clientRelationships),
clientAddresses: many(clientAddresses),
}));
// ─── Users ────────────────────────────────────────────────────────────────────
export const userProfilesRelations = relations(userProfiles, ({ many }) => ({
userPortRoles: many(userPortRoles),
}));
export const rolesRelations = relations(roles, ({ many }) => ({
userPortRoles: many(userPortRoles),
portRoleOverrides: many(portRoleOverrides),
}));
export const portRoleOverridesRelations = relations(portRoleOverrides, ({ one }) => ({
port: one(ports, {
fields: [portRoleOverrides.portId],
references: [ports.id],
}),
role: one(roles, {
fields: [portRoleOverrides.roleId],
references: [roles.id],
}),
}));
export const userPortRolesRelations = relations(userPortRoles, ({ one }) => ({
port: one(ports, {
fields: [userPortRoles.portId],
references: [ports.id],
}),
role: one(roles, {
fields: [userPortRoles.roleId],
references: [roles.id],
}),
}));
// ─── Clients ──────────────────────────────────────────────────────────────────
export const clientsRelations = relations(clients, ({ one, many }) => ({
port: one(ports, {
fields: [clients.portId],
references: [ports.id],
}),
contacts: many(clientContacts),
notes: many(clientNotes),
tags: many(clientTags),
interests: many(interests),
relationships_a: many(clientRelationships, { relationName: 'client_a' }),
relationships_b: many(clientRelationships, { relationName: 'client_b' }),
mergeLogsAsSurvivor: many(clientMergeLog),
documents: many(documents),
emailThreads: many(emailThreads),
reminders: many(reminders),
files: many(files),
waitingListEntries: many(berthWaitingList),
scratchpadNotes: many(scratchpadNotes),
formSubmissions: many(formSubmissions),
addresses: many(clientAddresses),
companyMemberships: many(companyMemberships),
berthReservations: many(berthReservations),
}));
export const clientContactsRelations = relations(clientContacts, ({ one }) => ({
client: one(clients, {
fields: [clientContacts.clientId],
references: [clients.id],
}),
}));
export const clientAddressesRelations = relations(clientAddresses, ({ one }) => ({
client: one(clients, {
fields: [clientAddresses.clientId],
references: [clients.id],
}),
port: one(ports, {
fields: [clientAddresses.portId],
references: [ports.id],
}),
}));
export const clientRelationshipsRelations = relations(clientRelationships, ({ one }) => ({
port: one(ports, {
fields: [clientRelationships.portId],
references: [ports.id],
}),
clientA: one(clients, {
fields: [clientRelationships.clientAId],
references: [clients.id],
relationName: 'client_a',
}),
clientB: one(clients, {
fields: [clientRelationships.clientBId],
references: [clients.id],
relationName: 'client_b',
}),
}));
export const clientNotesRelations = relations(clientNotes, ({ one }) => ({
client: one(clients, {
fields: [clientNotes.clientId],
references: [clients.id],
}),
}));
export const clientTagsRelations = relations(clientTags, ({ one }) => ({
client: one(clients, {
fields: [clientTags.clientId],
references: [clients.id],
}),
tag: one(tags, {
fields: [clientTags.tagId],
references: [tags.id],
}),
}));
export const clientMergeLogRelations = relations(clientMergeLog, ({ one }) => ({
port: one(ports, {
fields: [clientMergeLog.portId],
references: [ports.id],
}),
survivingClient: one(clients, {
fields: [clientMergeLog.survivingClientId],
references: [clients.id],
}),
}));
// ─── Interests ────────────────────────────────────────────────────────────────
export const interestsRelations = relations(interests, ({ one, many }) => ({
port: one(ports, {
fields: [interests.portId],
references: [ports.id],
}),
client: one(clients, {
fields: [interests.clientId],
references: [clients.id],
}),
yacht: one(yachts, {
fields: [interests.yachtId],
references: [yachts.id],
}),
notes: many(interestNotes),
tags: many(interestTags),
documents: many(documents),
reminders: many(reminders),
berthRecommendations: many(berthRecommendations),
formSubmissions: many(formSubmissions),
interestBerths: many(interestBerths),
}));
export const interestBerthsRelations = relations(interestBerths, ({ one }) => ({
interest: one(interests, {
fields: [interestBerths.interestId],
references: [interests.id],
}),
berth: one(berths, {
fields: [interestBerths.berthId],
references: [berths.id],
}),
}));
export const interestNotesRelations = relations(interestNotes, ({ one }) => ({
interest: one(interests, {
fields: [interestNotes.interestId],
references: [interests.id],
}),
}));
export const interestTagsRelations = relations(interestTags, ({ one }) => ({
interest: one(interests, {
fields: [interestTags.interestId],
references: [interests.id],
}),
tag: one(tags, {
fields: [interestTags.tagId],
references: [tags.id],
}),
}));
// ─── Yachts ───────────────────────────────────────────────────────────────────
export const yachtsRelations = relations(yachts, ({ one, many }) => ({
port: one(ports, {
fields: [yachts.portId],
references: [ports.id],
}),
ownershipHistory: many(yachtOwnershipHistory),
notes: many(yachtNotes),
tags: many(yachtTags),
interests: many(interests),
reservations: many(berthReservations),
documents: many(documents),
}));
export const yachtOwnershipHistoryRelations = relations(yachtOwnershipHistory, ({ one }) => ({
yacht: one(yachts, {
fields: [yachtOwnershipHistory.yachtId],
references: [yachts.id],
}),
}));
export const yachtNotesRelations = relations(yachtNotes, ({ one }) => ({
yacht: one(yachts, {
fields: [yachtNotes.yachtId],
references: [yachts.id],
}),
}));
export const yachtTagsRelations = relations(yachtTags, ({ one }) => ({
yacht: one(yachts, {
fields: [yachtTags.yachtId],
references: [yachts.id],
}),
tag: one(tags, {
fields: [yachtTags.tagId],
references: [tags.id],
}),
}));
// ─── Companies ────────────────────────────────────────────────────────────────
export const companiesRelations = relations(companies, ({ one, many }) => ({
port: one(ports, {
fields: [companies.portId],
references: [ports.id],
}),
memberships: many(companyMemberships),
addresses: many(companyAddresses),
notes: many(companyNotes),
tags: many(companyTags),
documents: many(documents),
files: many(files),
}));
export const companyMembershipsRelations = relations(companyMemberships, ({ one }) => ({
company: one(companies, {
fields: [companyMemberships.companyId],
references: [companies.id],
}),
client: one(clients, {
fields: [companyMemberships.clientId],
references: [clients.id],
}),
}));
export const companyAddressesRelations = relations(companyAddresses, ({ one }) => ({
company: one(companies, {
fields: [companyAddresses.companyId],
references: [companies.id],
}),
port: one(ports, {
fields: [companyAddresses.portId],
references: [ports.id],
}),
}));
export const companyNotesRelations = relations(companyNotes, ({ one }) => ({
company: one(companies, {
fields: [companyNotes.companyId],
references: [companies.id],
}),
}));
export const companyTagsRelations = relations(companyTags, ({ one }) => ({
company: one(companies, {
fields: [companyTags.companyId],
references: [companies.id],
}),
tag: one(tags, {
fields: [companyTags.tagId],
references: [tags.id],
}),
}));
// ─── Berths ───────────────────────────────────────────────────────────────────
export const berthsRelations = relations(berths, ({ one, many }) => ({
port: one(ports, {
fields: [berths.portId],
references: [ports.id],
}),
mapData: one(berthMapData),
recommendations: many(berthRecommendations),
waitingList: many(berthWaitingList),
maintenanceLogs: many(berthMaintenanceLog),
tags: many(berthTags),
interestBerths: many(interestBerths),
reminders: many(reminders),
}));
export const berthMapDataRelations = relations(berthMapData, ({ one }) => ({
berth: one(berths, {
fields: [berthMapData.berthId],
references: [berths.id],
}),
}));
export const berthRecommendationsRelations = relations(berthRecommendations, ({ one }) => ({
interest: one(interests, {
fields: [berthRecommendations.interestId],
references: [interests.id],
}),
berth: one(berths, {
fields: [berthRecommendations.berthId],
references: [berths.id],
}),
}));
export const berthWaitingListRelations = relations(berthWaitingList, ({ one }) => ({
berth: one(berths, {
fields: [berthWaitingList.berthId],
references: [berths.id],
}),
client: one(clients, {
fields: [berthWaitingList.clientId],
references: [clients.id],
}),
}));
export const berthMaintenanceLogRelations = relations(berthMaintenanceLog, ({ one }) => ({
berth: one(berths, {
fields: [berthMaintenanceLog.berthId],
references: [berths.id],
}),
port: one(ports, {
fields: [berthMaintenanceLog.portId],
references: [ports.id],
}),
}));
export const berthTagsRelations = relations(berthTags, ({ one }) => ({
berth: one(berths, {
fields: [berthTags.berthId],
references: [berths.id],
}),
tag: one(tags, {
fields: [berthTags.tagId],
references: [tags.id],
}),
}));
// ─── Berth Reservations ───────────────────────────────────────────────────────
export const berthReservationsRelations = relations(berthReservations, ({ one, many }) => ({
berth: one(berths, {
fields: [berthReservations.berthId],
references: [berths.id],
}),
port: one(ports, {
fields: [berthReservations.portId],
references: [ports.id],
}),
client: one(clients, {
fields: [berthReservations.clientId],
references: [clients.id],
}),
yacht: one(yachts, {
fields: [berthReservations.yachtId],
references: [yachts.id],
}),
interest: one(interests, {
fields: [berthReservations.interestId],
references: [interests.id],
}),
contractFile: one(files, {
fields: [berthReservations.contractFileId],
references: [files.id],
}),
documents: many(documents),
}));
// ─── Documents ────────────────────────────────────────────────────────────────
export const filesRelations = relations(files, ({ one, many }) => ({
port: one(ports, {
fields: [files.portId],
references: [ports.id],
}),
client: one(clients, {
fields: [files.clientId],
references: [clients.id],
}),
yacht: one(yachts, {
fields: [files.yachtId],
references: [yachts.id],
}),
company: one(companies, {
fields: [files.companyId],
references: [companies.id],
}),
documentAsFile: many(documents, { relationName: 'file' }),
documentAsSignedFile: many(documents, { relationName: 'signed_file' }),
}));
export const documentsRelations = relations(documents, ({ one, many }) => ({
port: one(ports, {
fields: [documents.portId],
references: [ports.id],
}),
interest: one(interests, {
fields: [documents.interestId],
references: [interests.id],
}),
client: one(clients, {
fields: [documents.clientId],
references: [clients.id],
}),
file: one(files, {
fields: [documents.fileId],
references: [files.id],
relationName: 'file',
}),
signedFile: one(files, {
fields: [documents.signedFileId],
references: [files.id],
relationName: 'signed_file',
}),
yacht: one(yachts, {
fields: [documents.yachtId],
references: [yachts.id],
}),
company: one(companies, {
fields: [documents.companyId],
references: [companies.id],
}),
reservation: one(berthReservations, {
fields: [documents.reservationId],
references: [berthReservations.id],
}),
signers: many(documentSigners),
events: many(documentEvents),
watchers: many(documentWatchers),
}));
export const documentSignersRelations = relations(documentSigners, ({ one, many }) => ({
document: one(documents, {
fields: [documentSigners.documentId],
references: [documents.id],
}),
events: many(documentEvents),
}));
export const documentEventsRelations = relations(documentEvents, ({ one }) => ({
document: one(documents, {
fields: [documentEvents.documentId],
references: [documents.id],
}),
signer: one(documentSigners, {
fields: [documentEvents.signerId],
references: [documentSigners.id],
}),
}));
export const documentTemplatesRelations = relations(documentTemplates, ({ one }) => ({
port: one(ports, {
fields: [documentTemplates.portId],
references: [ports.id],
}),
sourceFile: one(files, {
fields: [documentTemplates.sourceFileId],
references: [files.id],
}),
}));
export const documentWatchersRelations = relations(documentWatchers, ({ one }) => ({
document: one(documents, {
fields: [documentWatchers.documentId],
references: [documents.id],
}),
}));
export const formTemplatesRelations = relations(formTemplates, ({ one, many }) => ({
port: one(ports, {
fields: [formTemplates.portId],
references: [ports.id],
}),
submissions: many(formSubmissions),
}));
export const formSubmissionsRelations = relations(formSubmissions, ({ one }) => ({
formTemplate: one(formTemplates, {
fields: [formSubmissions.formTemplateId],
references: [formTemplates.id],
}),
client: one(clients, {
fields: [formSubmissions.clientId],
references: [clients.id],
}),
interest: one(interests, {
fields: [formSubmissions.interestId],
references: [interests.id],
}),
}));
// ─── Financial ────────────────────────────────────────────────────────────────
export const expensesRelations = relations(expenses, ({ one, many }) => ({
port: one(ports, {
fields: [expenses.portId],
references: [ports.id],
}),
invoiceExpenses: many(invoiceExpenses),
}));
export const invoicesRelations = relations(invoices, ({ one, many }) => ({
port: one(ports, {
fields: [invoices.portId],
references: [ports.id],
}),
pdfFile: one(files, {
fields: [invoices.pdfFileId],
references: [files.id],
}),
lineItems: many(invoiceLineItems),
invoiceExpenses: many(invoiceExpenses),
}));
export const invoiceLineItemsRelations = relations(invoiceLineItems, ({ one }) => ({
invoice: one(invoices, {
fields: [invoiceLineItems.invoiceId],
references: [invoices.id],
}),
}));
export const invoiceExpensesRelations = relations(invoiceExpenses, ({ one }) => ({
invoice: one(invoices, {
fields: [invoiceExpenses.invoiceId],
references: [invoices.id],
}),
expense: one(expenses, {
fields: [invoiceExpenses.expenseId],
references: [expenses.id],
}),
}));
// ─── Email ────────────────────────────────────────────────────────────────────
export const emailAccountsRelations = relations(emailAccounts, ({ one }) => ({
port: one(ports, {
fields: [emailAccounts.portId],
references: [ports.id],
}),
}));
export const emailThreadsRelations = relations(emailThreads, ({ one, many }) => ({
port: one(ports, {
fields: [emailThreads.portId],
references: [ports.id],
}),
client: one(clients, {
fields: [emailThreads.clientId],
references: [clients.id],
}),
messages: many(emailMessages),
}));
export const emailMessagesRelations = relations(emailMessages, ({ one }) => ({
thread: one(emailThreads, {
fields: [emailMessages.threadId],
references: [emailThreads.id],
}),
rawFile: one(files, {
fields: [emailMessages.rawFileId],
references: [files.id],
}),
}));
// ─── Operations ───────────────────────────────────────────────────────────────
export const remindersRelations = relations(reminders, ({ one, many }) => ({
port: one(ports, {
fields: [reminders.portId],
references: [ports.id],
}),
client: one(clients, {
fields: [reminders.clientId],
references: [clients.id],
}),
interest: one(interests, {
fields: [reminders.interestId],
references: [interests.id],
}),
berth: one(berths, {
fields: [reminders.berthId],
references: [berths.id],
}),
calendarCacheEntries: many(googleCalendarCache),
}));
export const googleCalendarTokensRelations = relations(googleCalendarTokens, ({ many }) => ({
cacheEntries: many(googleCalendarCache),
}));
export const googleCalendarCacheRelations = relations(googleCalendarCache, ({ one }) => ({
reminder: one(reminders, {
fields: [googleCalendarCache.reminderId],
references: [reminders.id],
}),
}));
export const notificationsRelations = relations(notifications, ({ one }) => ({
port: one(ports, {
fields: [notifications.portId],
references: [ports.id],
}),
}));
export const scheduledReportsRelations = relations(scheduledReports, ({ one, many }) => ({
port: one(ports, {
fields: [scheduledReports.portId],
references: [ports.id],
}),
recipients: many(reportRecipients),
generatedReports: many(generatedReports),
}));
export const reportRecipientsRelations = relations(reportRecipients, ({ one }) => ({
report: one(scheduledReports, {
fields: [reportRecipients.reportId],
references: [scheduledReports.id],
}),
}));
export const generatedReportsRelations = relations(generatedReports, ({ one }) => ({
port: one(ports, {
fields: [generatedReports.portId],
references: [ports.id],
}),
scheduledReport: one(scheduledReports, {
fields: [generatedReports.scheduledReportId],
references: [scheduledReports.id],
}),
file: one(files, {
fields: [generatedReports.fileId],
references: [files.id],
}),
}));
// ─── System ───────────────────────────────────────────────────────────────────
export const auditLogsRelations = relations(auditLogs, ({ one }) => ({
port: one(ports, {
fields: [auditLogs.portId],
references: [ports.id],
}),
revertOfLog: one(auditLogs, {
fields: [auditLogs.revertOf],
references: [auditLogs.id],
relationName: 'revert_of',
}),
}));
export const tagsRelations = relations(tags, ({ one, many }) => ({
port: one(ports, {
fields: [tags.portId],
references: [ports.id],
}),
clientTags: many(clientTags),
interestTags: many(interestTags),
berthTags: many(berthTags),
}));
export const webhooksRelations = relations(webhooks, ({ one, many }) => ({
port: one(ports, {
fields: [webhooks.portId],
references: [ports.id],
}),
deliveries: many(webhookDeliveries),
}));
export const webhookDeliveriesRelations = relations(webhookDeliveries, ({ one }) => ({
webhook: one(webhooks, {
fields: [webhookDeliveries.webhookId],
references: [webhooks.id],
}),
}));
export const systemSettingsRelations = relations(systemSettings, ({ one }) => ({
port: one(ports, {
fields: [systemSettings.portId],
references: [ports.id],
}),
}));
export const savedViewsRelations = relations(savedViews, ({ one }) => ({
port: one(ports, {
fields: [savedViews.portId],
references: [ports.id],
}),
}));
export const scratchpadNotesRelations = relations(scratchpadNotes, ({ one }) => ({
linkedClient: one(clients, {
fields: [scratchpadNotes.linkedClientId],
references: [clients.id],
}),
}));
export const userNotificationPreferencesRelations = relations(
userNotificationPreferences,
({ one }) => ({
port: one(ports, {
fields: [userNotificationPreferences.portId],
references: [ports.id],
}),
}),
);
export const customFieldDefinitionsRelations = relations(
customFieldDefinitions,
({ one, many }) => ({
port: one(ports, {
fields: [customFieldDefinitions.portId],
references: [ports.id],
}),
values: many(customFieldValues),
}),
);
export const customFieldValuesRelations = relations(customFieldValues, ({ one }) => ({
definition: one(customFieldDefinitions, {
fields: [customFieldValues.fieldId],
references: [customFieldDefinitions.id],
}),
}));
// ─── Residential ──────────────────────────────────────────────────────────────
export const residentialClientsRelations = relations(residentialClients, ({ one, many }) => ({
port: one(ports, {
fields: [residentialClients.portId],
references: [ports.id],
}),
interests: many(residentialInterests),
}));
export const residentialInterestsRelations = relations(residentialInterests, ({ one }) => ({
port: one(ports, {
fields: [residentialInterests.portId],
references: [ports.id],
}),
client: one(residentialClients, {
fields: [residentialInterests.residentialClientId],
references: [residentialClients.id],
}),
}));