diff --git a/src/app/db/migrations/0194_user_defined_deal_measures.sql b/src/app/db/migrations/0194_user_defined_deal_measures.sql new file mode 100644 index 00000000..1c11f2fa --- /dev/null +++ b/src/app/db/migrations/0194_user_defined_deal_measures.sql @@ -0,0 +1,16 @@ +CREATE TYPE "public"."user_defined_deal_measure_source" AS ENUM('instructed', 'pibi_ordered');--> statement-breakpoint +CREATE TABLE "user_defined_deal_measures" ( + "id" bigserial PRIMARY KEY NOT NULL, + "hubspot_deal_id" text NOT NULL, + "measure_name" text NOT NULL, + "source" "user_defined_deal_measure_source" NOT NULL, + "created_by_user_id" bigint NOT NULL, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "pushed_at" timestamp with time zone, + "confirmed_in_hubspot_at" timestamp with time zone, + "notes" text +); +--> statement-breakpoint +ALTER TABLE "user_defined_deal_measures" ADD CONSTRAINT "user_defined_deal_measures_created_by_user_id_user_id_fk" FOREIGN KEY ("created_by_user_id") REFERENCES "public"."user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +CREATE INDEX "idx_user_defined_deal_measures_deal_id" ON "user_defined_deal_measures" USING btree ("hubspot_deal_id");--> statement-breakpoint +CREATE INDEX "idx_user_defined_deal_measures_source" ON "user_defined_deal_measures" USING btree ("source"); diff --git a/src/app/db/migrations/meta/_journal.json b/src/app/db/migrations/meta/_journal.json index fe79b084..eb188e64 100644 --- a/src/app/db/migrations/meta/_journal.json +++ b/src/app/db/migrations/meta/_journal.json @@ -1359,6 +1359,13 @@ "when": 1777750000000, "tag": "0193_domna_survey_type", "breakpoints": true + }, + { + "idx": 194, + "version": "7", + "when": 1778100000000, + "tag": "0194_user_defined_deal_measures", + "breakpoints": true } ] } \ No newline at end of file diff --git a/src/app/db/schema/user_defined_deal_measures.ts b/src/app/db/schema/user_defined_deal_measures.ts new file mode 100644 index 00000000..0a059c0a --- /dev/null +++ b/src/app/db/schema/user_defined_deal_measures.ts @@ -0,0 +1,67 @@ +/** + * User-defined deal measures (issue #253) + * + * Tracks measures that an approver has *instructed* outside the coordinator's + * proposed set, plus measures that have been *PIBI-ordered* by the contractor + * (slice 4). The `source` enum distinguishes the two flows so a single table + * can back both. + * + * Each row pushes back to HubSpot under one of two new multi-value text deal + * properties (`instructed_measures` / `pibi_ordered_measures`). `pushed_at` + * is stamped after a successful sync; `confirmed_in_hubspot_at` is stamped + * by a follow-up reconcile job (out of scope for this slice). + */ +import { + bigserial, + text, + timestamp, + pgTable, + bigint, + index, + pgEnum, +} from "drizzle-orm/pg-core"; +import { user } from "./users"; +import { InferModel } from "drizzle-orm"; + +export const UserDefinedDealMeasureSource: [string, ...string[]] = [ + "instructed", + "pibi_ordered", +]; +export type UserDefinedDealMeasureSourceType = + | "instructed" + | "pibi_ordered"; + +export const userDefinedDealMeasureSourceEnum = pgEnum( + "user_defined_deal_measure_source", + UserDefinedDealMeasureSource as [string, ...string[]], +); + +export const userDefinedDealMeasures = pgTable( + "user_defined_deal_measures", + { + id: bigserial("id", { mode: "bigint" }).primaryKey(), + hubspotDealId: text("hubspot_deal_id").notNull(), + measureName: text("measure_name").notNull(), + source: userDefinedDealMeasureSourceEnum("source").notNull(), + createdByUserId: bigint("created_by_user_id", { mode: "bigint" }) + .notNull() + .references(() => user.id), + createdAt: timestamp("created_at", { withTimezone: true }) + .defaultNow() + .notNull(), + pushedAt: timestamp("pushed_at", { withTimezone: true }), + confirmedInHubspotAt: timestamp("confirmed_in_hubspot_at", { + withTimezone: true, + }), + notes: text("notes"), + }, + (table) => [ + index("idx_user_defined_deal_measures_deal_id").on(table.hubspotDealId), + index("idx_user_defined_deal_measures_source").on(table.source), + ], +); + +export type UserDefinedDealMeasure = InferModel< + typeof userDefinedDealMeasures, + "select" +>;