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:
2026-06-11 15:07:36 +02:00
parent 5d75752e2d
commit 0fd73ddcc0
+65 -3
View File
@@ -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 }) => ({
@@ -553,4 +611,8 @@ export type NewOfferPhaseService = typeof offer_phase_services.$inferInsert;
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 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;