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>
This commit is contained in:
89
src/lib/db/schema/interests.ts
Normal file
89
src/lib/db/schema/interests.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import {
|
||||
pgTable,
|
||||
text,
|
||||
boolean,
|
||||
integer,
|
||||
timestamp,
|
||||
primaryKey,
|
||||
index,
|
||||
} from 'drizzle-orm/pg-core';
|
||||
import { ports } from './ports';
|
||||
import { clients } from './clients';
|
||||
|
||||
// Pipeline stages: open, details_sent, in_communication, visited, signed_eoi_nda, deposit_10pct, contract, completed
|
||||
|
||||
export const interests = pgTable(
|
||||
'interests',
|
||||
{
|
||||
id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
|
||||
portId: text('port_id')
|
||||
.notNull()
|
||||
.references(() => ports.id),
|
||||
clientId: text('client_id')
|
||||
.notNull()
|
||||
.references(() => clients.id),
|
||||
berthId: text('berth_id'), // nullable — FK to berths defined in berths.ts, added via relation
|
||||
pipelineStage: text('pipeline_stage').notNull().default('open'),
|
||||
leadCategory: text('lead_category'), // general_interest, specific_qualified, hot_lead
|
||||
source: text('source'), // website, manual, referral, broker
|
||||
eoiStatus: text('eoi_status'), // null, waiting_for_signatures, signed, expired
|
||||
documensoId: text('documenso_id'),
|
||||
contractStatus: text('contract_status'),
|
||||
depositStatus: text('deposit_status'),
|
||||
reservationStatus: text('reservation_status'),
|
||||
dateFirstContact: timestamp('date_first_contact', { withTimezone: true }),
|
||||
dateLastContact: timestamp('date_last_contact', { withTimezone: true }),
|
||||
dateEoiSent: timestamp('date_eoi_sent', { withTimezone: true }),
|
||||
dateEoiSigned: timestamp('date_eoi_signed', { withTimezone: true }),
|
||||
dateContractSent: timestamp('date_contract_sent', { withTimezone: true }),
|
||||
dateContractSigned: timestamp('date_contract_signed', { withTimezone: true }),
|
||||
dateDepositReceived: timestamp('date_deposit_received', { withTimezone: true }),
|
||||
reminderEnabled: boolean('reminder_enabled').notNull().default(false),
|
||||
reminderDays: integer('reminder_days'),
|
||||
reminderLastFired: timestamp('reminder_last_fired', { withTimezone: true }),
|
||||
notes: text('notes'),
|
||||
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_interests_port').on(table.portId),
|
||||
index('idx_interests_client').on(table.clientId),
|
||||
index('idx_interests_berth').on(table.berthId),
|
||||
index('idx_interests_stage').on(table.portId, table.pipelineStage),
|
||||
index('idx_interests_archived').on(table.portId, table.archivedAt),
|
||||
],
|
||||
);
|
||||
|
||||
export const interestNotes = pgTable(
|
||||
'interest_notes',
|
||||
{
|
||||
id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
|
||||
interestId: text('interest_id')
|
||||
.notNull()
|
||||
.references(() => interests.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_in_interest').on(table.interestId)],
|
||||
);
|
||||
|
||||
export const interestTags = pgTable(
|
||||
'interest_tags',
|
||||
{
|
||||
interestId: text('interest_id')
|
||||
.notNull()
|
||||
.references(() => interests.id, { onDelete: 'cascade' }),
|
||||
tagId: text('tag_id').notNull(), // references tags.id
|
||||
},
|
||||
(table) => [primaryKey({ columns: [table.interestId, table.tagId] })],
|
||||
);
|
||||
|
||||
export type Interest = typeof interests.$inferSelect;
|
||||
export type NewInterest = typeof interests.$inferInsert;
|
||||
export type InterestNote = typeof interestNotes.$inferSelect;
|
||||
export type NewInterestNote = typeof interestNotes.$inferInsert;
|
||||
Reference in New Issue
Block a user