feat(12-01): extend offer schema with tier, pricing, and category fields
- Add additive offer_macros columns: description, category, ticket, is_archived, and structured transformation-promise fields (cliente_ideale/risultato/tempo/pain/metodo) - Add additive offer_micros columns: tier_letter (A/B/C, CHECK constraint added in migration 0008) and public_price - Add new offer_tier_services junction table (offer_micros <-> services), relations, and types - Extend offerMicrosRelations with tierServices; legacy offer_micro_services/offer_services untouched
This commit is contained in:
@@ -259,6 +259,10 @@ export const settings = pgTable("settings", {
|
||||
});
|
||||
|
||||
// ============ OFFER MACROS ============
|
||||
// Phase 12 additive columns (Offer Editor — Tier A/B/C, Tag & Prezzo Pubblico):
|
||||
// description, category, ticket, is_archived, and the 5 structured
|
||||
// transformation-promise fields (cliente_ideale/risultato/tempo/pain/metodo).
|
||||
// transformation_promise (legacy, free-text) stays untouched.
|
||||
export const offer_macros = pgTable("offer_macros", {
|
||||
id: text("id")
|
||||
.primaryKey()
|
||||
@@ -266,6 +270,16 @@ export const offer_macros = pgTable("offer_macros", {
|
||||
internal_name: text("internal_name").notNull(),
|
||||
public_name: text("public_name").notNull(),
|
||||
transformation_promise: text("transformation_promise"),
|
||||
// Phase 12 additive fields below
|
||||
description: text("description"), // short description shown on offer cards
|
||||
category: text("category"), // single-select "Categoria" (Entry/Signature/Retainer Offer) — OFFER-15/18
|
||||
ticket: text("ticket"), // single-select "Ticket" (Low/Mid/High Ticket) — OFFER-15
|
||||
is_archived: boolean("is_archived").notNull().default(false), // OFFER-18 archive flag
|
||||
cliente_ideale: text("cliente_ideale"), // "Aiuto: [Cliente Ideale]"
|
||||
risultato: text("risultato"), // "A ottenere: [Risultato]"
|
||||
tempo: text("tempo"), // "In: [tempo]"
|
||||
pain: text("pain"), // "Senza: [Pain]"
|
||||
metodo: text("metodo"), // "Grazie a: [Metodo]"
|
||||
sort_order: integer("sort_order").notNull().default(0),
|
||||
created_at: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
});
|
||||
@@ -283,6 +297,15 @@ export const offer_micros = pgTable("offer_micros", {
|
||||
transformation_promise: text("transformation_promise"),
|
||||
duration_months: integer("duration_months").notNull().default(1),
|
||||
sort_order: integer("sort_order").notNull().default(0),
|
||||
// Phase 12 additive fields below.
|
||||
// tier_letter: nullable, values constrained to 'A'|'B'|'C' via a CHECK
|
||||
// constraint at the SQL level (migration 0008) — not enforced in Drizzle
|
||||
// (no native CHECK helper in this project's pg-core version); validated in
|
||||
// Zod at the server-action layer (Plan 03).
|
||||
tier_letter: text("tier_letter"),
|
||||
// public_price: manual public price per tier (D-5/OFFER-16), independent of
|
||||
// the computed services total (computed at query time, never stored).
|
||||
public_price: numeric("public_price", { precision: 10, scale: 2 }),
|
||||
});
|
||||
|
||||
// ============ OFFER SERVICES (distinct from service_catalog — marketing/transformation semantics) ============
|
||||
@@ -312,6 +335,28 @@ export const offer_micro_services = pgTable(
|
||||
})
|
||||
);
|
||||
|
||||
// ============ OFFER TIER SERVICES (Phase 12 — junction: offer_micros <-> services) ============
|
||||
// Additive replacement for the legacy offer_micro_services (which points to the
|
||||
// deprecated offer_services table). This junction connects a tier (offer_micros row
|
||||
// with tier_letter set) to the unified services catalog (Phase 11). Do NOT modify
|
||||
// offer_micro_services — it remains untouched for legacy data.
|
||||
export const offer_tier_services = pgTable(
|
||||
"offer_tier_services",
|
||||
{
|
||||
tier_id: text("tier_id")
|
||||
.notNull()
|
||||
.references(() => offer_micros.id, { onDelete: "cascade" }),
|
||||
service_id: text("service_id")
|
||||
.notNull()
|
||||
.references(() => services.id, { onDelete: "cascade" }),
|
||||
created_at: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
},
|
||||
(t) => ({
|
||||
pk: primaryKey({ columns: [t.tier_id, t.service_id] }),
|
||||
tierIdx: index("offer_tier_services_tier_idx").on(t.tier_id),
|
||||
})
|
||||
);
|
||||
|
||||
// ============ PROJECT OFFERS (assignment of micro-offer to project) ============
|
||||
export const project_offers = pgTable("project_offers", {
|
||||
id: text("id")
|
||||
@@ -549,6 +594,7 @@ export const offerMacrosRelations = relations(offer_macros, ({ many }) => ({
|
||||
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),
|
||||
tierServices: many(offer_tier_services),
|
||||
projectOffers: many(project_offers),
|
||||
}));
|
||||
|
||||
@@ -561,6 +607,11 @@ export const offerMicroServicesRelations = relations(offer_micro_services, ({ on
|
||||
service: one(offer_services, { fields: [offer_micro_services.service_id], references: [offer_services.id] }),
|
||||
}));
|
||||
|
||||
export const offerTierServicesRelations = relations(offer_tier_services, ({ one }) => ({
|
||||
tier: one(offer_micros, { fields: [offer_tier_services.tier_id], references: [offer_micros.id] }),
|
||||
service: one(services, { fields: [offer_tier_services.service_id], references: [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] }),
|
||||
@@ -638,6 +689,8 @@ 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 OfferTierService = typeof offer_tier_services.$inferSelect;
|
||||
export type NewOfferTierService = typeof offer_tier_services.$inferInsert;
|
||||
export type ProjectOffer = typeof project_offers.$inferSelect;
|
||||
export type NewProjectOffer = typeof project_offers.$inferInsert;
|
||||
export type OfferPhase = typeof offer_phases.$inferSelect;
|
||||
|
||||
Reference in New Issue
Block a user