feat(05-01): append five offer tables + Drizzle relations to schema.ts
- Add primaryKey to drizzle-orm/pg-core import - Define offer_macros, offer_micros, offer_services, offer_micro_services, project_offers - Add Drizzle relations for all five new tables (+ projectOffers in projectsRelations) - Export TypeScript infer types for all new tables
This commit is contained in:
+109
-1
@@ -5,6 +5,7 @@ import {
|
|||||||
numeric,
|
numeric,
|
||||||
timestamp,
|
timestamp,
|
||||||
boolean,
|
boolean,
|
||||||
|
primaryKey,
|
||||||
} from "drizzle-orm/pg-core";
|
} from "drizzle-orm/pg-core";
|
||||||
import { relations } from "drizzle-orm";
|
import { relations } from "drizzle-orm";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
@@ -194,6 +195,78 @@ export const settings = pgTable("settings", {
|
|||||||
updated_at: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
updated_at: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ============ OFFER MACROS ============
|
||||||
|
export const offer_macros = pgTable("offer_macros", {
|
||||||
|
id: text("id")
|
||||||
|
.primaryKey()
|
||||||
|
.$defaultFn(() => nanoid()),
|
||||||
|
internal_name: text("internal_name").notNull(),
|
||||||
|
public_name: text("public_name").notNull(),
|
||||||
|
transformation_promise: text("transformation_promise"),
|
||||||
|
sort_order: integer("sort_order").notNull().default(0),
|
||||||
|
created_at: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// ============ OFFER MICROS ============
|
||||||
|
export const offer_micros = pgTable("offer_micros", {
|
||||||
|
id: text("id")
|
||||||
|
.primaryKey()
|
||||||
|
.$defaultFn(() => nanoid()),
|
||||||
|
macro_id: text("macro_id")
|
||||||
|
.notNull()
|
||||||
|
.references(() => offer_macros.id, { onDelete: "cascade" }),
|
||||||
|
internal_name: text("internal_name").notNull(),
|
||||||
|
public_name: text("public_name").notNull(),
|
||||||
|
transformation_promise: text("transformation_promise"),
|
||||||
|
duration_months: integer("duration_months").notNull().default(1),
|
||||||
|
sort_order: integer("sort_order").notNull().default(0),
|
||||||
|
});
|
||||||
|
|
||||||
|
// ============ OFFER SERVICES (distinct from service_catalog — marketing/transformation semantics) ============
|
||||||
|
export const offer_services = pgTable("offer_services", {
|
||||||
|
id: text("id")
|
||||||
|
.primaryKey()
|
||||||
|
.$defaultFn(() => nanoid()),
|
||||||
|
name: text("name").notNull(),
|
||||||
|
price: numeric("price", { precision: 10, scale: 2 }).notNull(),
|
||||||
|
transformation_description: text("transformation_description"),
|
||||||
|
active: boolean("active").notNull().default(true),
|
||||||
|
});
|
||||||
|
|
||||||
|
// ============ OFFER MICRO SERVICES (junction: offer_micros <-> offer_services) ============
|
||||||
|
export const offer_micro_services = pgTable(
|
||||||
|
"offer_micro_services",
|
||||||
|
{
|
||||||
|
micro_id: text("micro_id")
|
||||||
|
.notNull()
|
||||||
|
.references(() => offer_micros.id, { onDelete: "cascade" }),
|
||||||
|
service_id: text("service_id")
|
||||||
|
.notNull()
|
||||||
|
.references(() => offer_services.id, { onDelete: "cascade" }),
|
||||||
|
},
|
||||||
|
(t) => ({
|
||||||
|
pk: primaryKey({ columns: [t.micro_id, t.service_id] }),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// ============ PROJECT OFFERS (assignment of micro-offer to project) ============
|
||||||
|
export const project_offers = pgTable("project_offers", {
|
||||||
|
id: text("id")
|
||||||
|
.primaryKey()
|
||||||
|
.$defaultFn(() => nanoid()),
|
||||||
|
project_id: text("project_id")
|
||||||
|
.notNull()
|
||||||
|
.references(() => projects.id, { onDelete: "cascade" }),
|
||||||
|
micro_id: text("micro_id")
|
||||||
|
.notNull()
|
||||||
|
.references(() => offer_micros.id, { onDelete: "restrict" }),
|
||||||
|
// NOT NULL with defaultNow — required for forecast computation (null start_date breaks forecast)
|
||||||
|
start_date: timestamp("start_date", { withTimezone: true }).notNull().defaultNow(),
|
||||||
|
// Offer-level accepted total — separate from projects.accepted_total (quote builder total)
|
||||||
|
accepted_total: numeric("accepted_total", { precision: 10, scale: 2 }),
|
||||||
|
created_at: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||||
|
});
|
||||||
|
|
||||||
// ============ RELATIONS ============
|
// ============ RELATIONS ============
|
||||||
|
|
||||||
export const clientsRelations = relations(clients, ({ many }) => ({
|
export const clientsRelations = relations(clients, ({ many }) => ({
|
||||||
@@ -208,6 +281,7 @@ export const projectsRelations = relations(projects, ({ one, many }) => ({
|
|||||||
notes: many(notes),
|
notes: many(notes),
|
||||||
quote_items: many(quote_items),
|
quote_items: many(quote_items),
|
||||||
time_entries: many(time_entries),
|
time_entries: many(time_entries),
|
||||||
|
projectOffers: many(project_offers),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const phasesRelations = relations(phases, ({ one, many }) => ({
|
export const phasesRelations = relations(phases, ({ one, many }) => ({
|
||||||
@@ -268,6 +342,30 @@ export const serviceCatalogRelations = relations(
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const offerMacrosRelations = relations(offer_macros, ({ many }) => ({
|
||||||
|
micros: many(offer_micros),
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const offerMicrosRelations = relations(offer_micros, ({ one, many }) => ({
|
||||||
|
macro: one(offer_macros, { fields: [offer_micros.macro_id], references: [offer_macros.id] }),
|
||||||
|
services: many(offer_micro_services),
|
||||||
|
projectOffers: many(project_offers),
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const offerServicesRelations = relations(offer_services, ({ many }) => ({
|
||||||
|
microAssignments: many(offer_micro_services),
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const offerMicroServicesRelations = relations(offer_micro_services, ({ one }) => ({
|
||||||
|
micro: one(offer_micros, { fields: [offer_micro_services.micro_id], references: [offer_micros.id] }),
|
||||||
|
service: one(offer_services, { fields: [offer_micro_services.service_id], references: [offer_services.id] }),
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const projectOffersRelations = relations(project_offers, ({ one }) => ({
|
||||||
|
project: one(projects, { fields: [project_offers.project_id], references: [projects.id] }),
|
||||||
|
micro: one(offer_micros, { fields: [project_offers.micro_id], references: [offer_micros.id] }),
|
||||||
|
}));
|
||||||
|
|
||||||
// ============ TYPESCRIPT TYPES (for use in API routes and Server Components) ============
|
// ============ TYPESCRIPT TYPES (for use in API routes and Server Components) ============
|
||||||
|
|
||||||
export type Client = typeof clients.$inferSelect;
|
export type Client = typeof clients.$inferSelect;
|
||||||
@@ -295,4 +393,14 @@ export type NewQuoteItem = typeof quote_items.$inferInsert;
|
|||||||
export type TimeEntry = typeof time_entries.$inferSelect;
|
export type TimeEntry = typeof time_entries.$inferSelect;
|
||||||
export type NewTimeEntry = typeof time_entries.$inferInsert;
|
export type NewTimeEntry = typeof time_entries.$inferInsert;
|
||||||
export type Setting = typeof settings.$inferSelect;
|
export type Setting = typeof settings.$inferSelect;
|
||||||
export type NewSetting = typeof settings.$inferInsert;
|
export type NewSetting = typeof settings.$inferInsert;
|
||||||
|
export type OfferMacro = typeof offer_macros.$inferSelect;
|
||||||
|
export type NewOfferMacro = typeof offer_macros.$inferInsert;
|
||||||
|
export type OfferMicro = typeof offer_micros.$inferSelect;
|
||||||
|
export type NewOfferMicro = typeof offer_micros.$inferInsert;
|
||||||
|
export type OfferService = typeof offer_services.$inferSelect;
|
||||||
|
export type NewOfferService = typeof offer_services.$inferInsert;
|
||||||
|
export type OfferMicroService = typeof offer_micro_services.$inferSelect;
|
||||||
|
export type NewOfferMicroService = typeof offer_micro_services.$inferInsert;
|
||||||
|
export type ProjectOffer = typeof project_offers.$inferSelect;
|
||||||
|
export type NewProjectOffer = typeof project_offers.$inferInsert;
|
||||||
Reference in New Issue
Block a user