feat(10): add CRM schema foundation - leads, activities, reminders tables
- Expand leads table with CRM fields (phone, company, status, last_contact_date, next_action, notes) - Add activities table for immutable interaction history - Add reminders table for follow-up scheduling - Add relations: leads ↔ activities, leads ↔ reminders - Add TypeScript types: Activity, NewActivity, Reminder, NewReminder - Status enum: contacted → qualified → proposal_sent → negotiating → won → lost - Activity types: call, email, meeting, note - Cascade delete for data integrity - npm run build: PASSED (0 errors) Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
+64
-2
@@ -362,14 +362,62 @@ export const quotes = pgTable("quotes", {
|
||||
.defaultNow(),
|
||||
});
|
||||
|
||||
// ============ LEADS TABLE (CRM — Phase 10, but needed for quotes FK) ============
|
||||
// Placeholder table for Phase 10 CRM integration
|
||||
// ============ LEADS TABLE (CRM — Phase 10) ============
|
||||
export const leads = pgTable("leads", {
|
||||
id: text("id")
|
||||
.primaryKey()
|
||||
.$defaultFn(() => nanoid()),
|
||||
name: text("name").notNull(),
|
||||
email: text("email"),
|
||||
phone: text("phone"),
|
||||
company: text("company"),
|
||||
status: text("status")
|
||||
.notNull()
|
||||
.default("contacted"), // contacted | qualified | proposal_sent | negotiating | won | lost
|
||||
last_contact_date: timestamp("last_contact_date", { withTimezone: true }),
|
||||
next_action: text("next_action"),
|
||||
next_action_date: timestamp("next_action_date", { withTimezone: true }),
|
||||
notes: text("notes"),
|
||||
created_at: timestamp("created_at", { withTimezone: true })
|
||||
.notNull()
|
||||
.defaultNow(),
|
||||
updated_at: timestamp("updated_at", { withTimezone: true })
|
||||
.notNull()
|
||||
.defaultNow(),
|
||||
});
|
||||
|
||||
// ============ ACTIVITIES TABLE (CRM interaction history) ============
|
||||
export const activities = pgTable("activities", {
|
||||
id: text("id")
|
||||
.primaryKey()
|
||||
.$defaultFn(() => nanoid()),
|
||||
lead_id: text("lead_id")
|
||||
.notNull()
|
||||
.references(() => leads.id, { onDelete: "cascade" }),
|
||||
type: text("type")
|
||||
.notNull(), // call | email | meeting | note
|
||||
duration_minutes: integer("duration_minutes"), // optional, for calls/meetings
|
||||
notes: text("notes").notNull(),
|
||||
activity_date: timestamp("activity_date", { withTimezone: true })
|
||||
.notNull(),
|
||||
created_at: timestamp("created_at", { withTimezone: true })
|
||||
.notNull()
|
||||
.defaultNow(),
|
||||
});
|
||||
|
||||
// ============ REMINDERS TABLE (CRM follow-up reminders) ============
|
||||
export const reminders = pgTable("reminders", {
|
||||
id: text("id")
|
||||
.primaryKey()
|
||||
.$defaultFn(() => nanoid()),
|
||||
lead_id: text("lead_id")
|
||||
.notNull()
|
||||
.references(() => leads.id, { onDelete: "cascade" }),
|
||||
title: text("title").notNull(),
|
||||
description: text("description"),
|
||||
due_date: timestamp("due_date", { withTimezone: true })
|
||||
.notNull(),
|
||||
completed_at: timestamp("completed_at", { withTimezone: true }),
|
||||
created_at: timestamp("created_at", { withTimezone: true })
|
||||
.notNull()
|
||||
.defaultNow(),
|
||||
@@ -497,6 +545,16 @@ export const offerPhaseServicesRelations = relations(offer_phase_services, ({ on
|
||||
|
||||
export const leadsRelations = relations(leads, ({ many }) => ({
|
||||
quotes: many(quotes),
|
||||
activities: many(activities),
|
||||
reminders: many(reminders),
|
||||
}));
|
||||
|
||||
export const activitiesRelations = relations(activities, ({ one }) => ({
|
||||
lead: one(leads, { fields: [activities.lead_id], references: [leads.id] }),
|
||||
}));
|
||||
|
||||
export const remindersRelations = relations(reminders, ({ one }) => ({
|
||||
lead: one(leads, { fields: [reminders.lead_id], references: [leads.id] }),
|
||||
}));
|
||||
|
||||
export const quotesRelations = relations(quotes, ({ one, many }) => ({
|
||||
@@ -554,3 +612,7 @@ export type Quote = typeof quotes.$inferSelect;
|
||||
export type NewQuote = typeof quotes.$inferInsert;
|
||||
export type Lead = typeof leads.$inferSelect;
|
||||
export type NewLead = typeof leads.$inferInsert;
|
||||
export type Activity = typeof activities.$inferSelect;
|
||||
export type NewActivity = typeof activities.$inferInsert;
|
||||
export type Reminder = typeof reminders.$inferSelect;
|
||||
export type NewReminder = typeof reminders.$inferInsert;
|
||||
Reference in New Issue
Block a user