Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
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',
|
|
|
|
|
{
|
2026-04-14 12:44:11 -04:00
|
|
|
id: text('id')
|
|
|
|
|
.primaryKey()
|
|
|
|
|
.$defaultFn(() => crypto.randomUUID()),
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
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',
|
|
|
|
|
{
|
2026-04-14 12:44:11 -04:00
|
|
|
id: text('id')
|
|
|
|
|
.primaryKey()
|
|
|
|
|
.$defaultFn(() => crypto.randomUUID()),
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
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),
|
2026-04-14 12:44:11 -04:00
|
|
|
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'`),
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
],
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
export const clientRelationships = pgTable(
|
|
|
|
|
'client_relationships',
|
|
|
|
|
{
|
2026-04-14 12:44:11 -04:00
|
|
|
id: text('id')
|
|
|
|
|
.primaryKey()
|
|
|
|
|
.$defaultFn(() => crypto.randomUUID()),
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
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',
|
|
|
|
|
{
|
2026-04-14 12:44:11 -04:00
|
|
|
id: text('id')
|
|
|
|
|
.primaryKey()
|
|
|
|
|
.$defaultFn(() => crypto.randomUUID()),
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
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',
|
|
|
|
|
{
|
2026-04-14 12:44:11 -04:00
|
|
|
id: text('id')
|
|
|
|
|
.primaryKey()
|
|
|
|
|
.$defaultFn(() => crypto.randomUUID()),
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
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)],
|
|
|
|
|
);
|
|
|
|
|
|
2026-04-14 12:44:11 -04:00
|
|
|
export const clientAddresses = pgTable(
|
|
|
|
|
'client_addresses',
|
|
|
|
|
{
|
|
|
|
|
id: text('id')
|
|
|
|
|
.primaryKey()
|
|
|
|
|
.$defaultFn(() => crypto.randomUUID()),
|
|
|
|
|
clientId: text('client_id')
|
|
|
|
|
.notNull()
|
|
|
|
|
.references(() => clients.id, { onDelete: 'cascade' }),
|
|
|
|
|
portId: text('port_id')
|
|
|
|
|
.notNull()
|
|
|
|
|
.references(() => ports.id, { onDelete: 'cascade' }),
|
|
|
|
|
label: text('label').notNull().default('Primary'),
|
|
|
|
|
streetAddress: text('street_address'),
|
|
|
|
|
city: text('city'),
|
|
|
|
|
stateProvince: text('state_province'),
|
|
|
|
|
postalCode: text('postal_code'),
|
|
|
|
|
country: text('country'),
|
|
|
|
|
isPrimary: boolean('is_primary').notNull().default(true),
|
|
|
|
|
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
|
|
|
|
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
|
|
|
|
|
},
|
|
|
|
|
(table) => [index('idx_ca_client').on(table.clientId), index('idx_ca_port').on(table.portId)],
|
|
|
|
|
);
|
|
|
|
|
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
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;
|
2026-04-14 12:44:11 -04:00
|
|
|
export type ClientAddress = typeof clientAddresses.$inferSelect;
|
|
|
|
|
export type NewClientAddress = typeof clientAddresses.$inferInsert;
|