150 lines
5.9 KiB
TypeScript
150 lines
5.9 KiB
TypeScript
|
|
import {
|
||
|
|
pgTable,
|
||
|
|
text,
|
||
|
|
boolean,
|
||
|
|
timestamp,
|
||
|
|
numeric,
|
||
|
|
jsonb,
|
||
|
|
index,
|
||
|
|
primaryKey,
|
||
|
|
} from 'drizzle-orm/pg-core';
|
||
|
|
import { sql } from 'drizzle-orm';
|
||
|
|
import { ports } from './ports';
|
||
|
|
|
||
|
|
export const clients = pgTable(
|
||
|
|
'clients',
|
||
|
|
{
|
||
|
|
id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
|
||
|
|
portId: text('port_id')
|
||
|
|
.notNull()
|
||
|
|
.references(() => ports.id),
|
||
|
|
fullName: text('full_name').notNull(),
|
||
|
|
companyName: text('company_name'),
|
||
|
|
nationality: text('nationality'),
|
||
|
|
isProxy: boolean('is_proxy').notNull().default(false),
|
||
|
|
proxyType: text('proxy_type'), // broker, representative, family_member, legal_counsel, other
|
||
|
|
actualOwnerName: text('actual_owner_name'),
|
||
|
|
relationshipNotes: text('relationship_notes'),
|
||
|
|
yachtName: text('yacht_name'),
|
||
|
|
yachtLengthFt: numeric('yacht_length_ft'),
|
||
|
|
yachtWidthFt: numeric('yacht_width_ft'),
|
||
|
|
yachtDraftFt: numeric('yacht_draft_ft'),
|
||
|
|
yachtLengthM: numeric('yacht_length_m'),
|
||
|
|
yachtWidthM: numeric('yacht_width_m'),
|
||
|
|
yachtDraftM: numeric('yacht_draft_m'),
|
||
|
|
berthSizeDesired: text('berth_size_desired'),
|
||
|
|
preferredContactMethod: text('preferred_contact_method'), // email, phone, whatsapp
|
||
|
|
preferredLanguage: text('preferred_language'),
|
||
|
|
timezone: text('timezone'),
|
||
|
|
source: text('source'), // website, manual, referral, broker
|
||
|
|
sourceDetails: text('source_details'),
|
||
|
|
archivedAt: timestamp('archived_at', { withTimezone: true }),
|
||
|
|
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||
|
|
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
|
||
|
|
},
|
||
|
|
(table) => [
|
||
|
|
index('idx_clients_port').on(table.portId),
|
||
|
|
index('idx_clients_name').on(table.portId, table.fullName),
|
||
|
|
index('idx_clients_archived').on(table.portId, table.archivedAt),
|
||
|
|
],
|
||
|
|
);
|
||
|
|
|
||
|
|
export const clientContacts = pgTable(
|
||
|
|
'client_contacts',
|
||
|
|
{
|
||
|
|
id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
|
||
|
|
clientId: text('client_id')
|
||
|
|
.notNull()
|
||
|
|
.references(() => clients.id, { onDelete: 'cascade' }),
|
||
|
|
channel: text('channel').notNull(), // email, phone, whatsapp, other
|
||
|
|
value: text('value').notNull(),
|
||
|
|
label: text('label'), // primary, secondary, work, personal, broker, assistant
|
||
|
|
isPrimary: boolean('is_primary').notNull().default(false),
|
||
|
|
notes: text('notes'),
|
||
|
|
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||
|
|
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
|
||
|
|
},
|
||
|
|
(table) => [
|
||
|
|
index('idx_cc_client').on(table.clientId),
|
||
|
|
index('idx_cc_email').on(table.channel, table.value).where(sql`${table.channel} = 'email'`),
|
||
|
|
index('idx_cc_phone').on(table.channel, table.value).where(sql`${table.channel} = 'phone'`),
|
||
|
|
],
|
||
|
|
);
|
||
|
|
|
||
|
|
export const clientRelationships = pgTable(
|
||
|
|
'client_relationships',
|
||
|
|
{
|
||
|
|
id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
|
||
|
|
portId: text('port_id')
|
||
|
|
.notNull()
|
||
|
|
.references(() => ports.id),
|
||
|
|
clientAId: text('client_a_id')
|
||
|
|
.notNull()
|
||
|
|
.references(() => clients.id, { onDelete: 'cascade' }),
|
||
|
|
clientBId: text('client_b_id')
|
||
|
|
.notNull()
|
||
|
|
.references(() => clients.id, { onDelete: 'cascade' }),
|
||
|
|
relationshipType: text('relationship_type').notNull(), // referred_by, broker_for, family_member, same_vessel, custom
|
||
|
|
description: text('description'),
|
||
|
|
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||
|
|
},
|
||
|
|
(table) => [index('idx_cr_port').on(table.portId)],
|
||
|
|
);
|
||
|
|
|
||
|
|
export const clientNotes = pgTable(
|
||
|
|
'client_notes',
|
||
|
|
{
|
||
|
|
id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
|
||
|
|
clientId: text('client_id')
|
||
|
|
.notNull()
|
||
|
|
.references(() => clients.id, { onDelete: 'cascade' }),
|
||
|
|
authorId: text('author_id').notNull(), // user ID
|
||
|
|
content: text('content').notNull(),
|
||
|
|
mentions: text('mentions').array(), // array of mentioned user IDs
|
||
|
|
isLocked: boolean('is_locked').notNull().default(false),
|
||
|
|
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||
|
|
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
|
||
|
|
},
|
||
|
|
(table) => [index('idx_cn_client').on(table.clientId)],
|
||
|
|
);
|
||
|
|
|
||
|
|
export const clientTags = pgTable(
|
||
|
|
'client_tags',
|
||
|
|
{
|
||
|
|
clientId: text('client_id')
|
||
|
|
.notNull()
|
||
|
|
.references(() => clients.id, { onDelete: 'cascade' }),
|
||
|
|
tagId: text('tag_id').notNull(), // references tags.id — defined later in system.ts
|
||
|
|
},
|
||
|
|
(table) => [primaryKey({ columns: [table.clientId, table.tagId] })],
|
||
|
|
);
|
||
|
|
|
||
|
|
export const clientMergeLog = pgTable(
|
||
|
|
'client_merge_log',
|
||
|
|
{
|
||
|
|
id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
|
||
|
|
portId: text('port_id')
|
||
|
|
.notNull()
|
||
|
|
.references(() => ports.id),
|
||
|
|
survivingClientId: text('surviving_client_id')
|
||
|
|
.notNull()
|
||
|
|
.references(() => clients.id),
|
||
|
|
mergedClientId: text('merged_client_id').notNull(), // the client that was merged away (may no longer exist)
|
||
|
|
mergedBy: text('merged_by').notNull(), // user ID
|
||
|
|
mergeDetails: jsonb('merge_details').notNull(), // which fields were kept from which record
|
||
|
|
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||
|
|
},
|
||
|
|
(table) => [index('idx_cml_port').on(table.portId)],
|
||
|
|
);
|
||
|
|
|
||
|
|
export type Client = typeof clients.$inferSelect;
|
||
|
|
export type NewClient = typeof clients.$inferInsert;
|
||
|
|
export type ClientContact = typeof clientContacts.$inferSelect;
|
||
|
|
export type NewClientContact = typeof clientContacts.$inferInsert;
|
||
|
|
export type ClientRelationship = typeof clientRelationships.$inferSelect;
|
||
|
|
export type NewClientRelationship = typeof clientRelationships.$inferInsert;
|
||
|
|
export type ClientNote = typeof clientNotes.$inferSelect;
|
||
|
|
export type NewClientNote = typeof clientNotes.$inferInsert;
|
||
|
|
export type ClientMergeLog = typeof clientMergeLog.$inferSelect;
|
||
|
|
export type NewClientMergeLog = typeof clientMergeLog.$inferInsert;
|