add user_defined_deal_measures table migration and schema

Track instructed and pibi-ordered measures locally with a source enum,
created-by user, and HubSpot sync timestamps so issue #253 can persist
approver instructions and slice 4 can reuse the table for PIBI selections.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-05-05 18:56:09 +00:00
parent a69dac1fca
commit 54e093891d
3 changed files with 90 additions and 0 deletions

View file

@ -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");

View file

@ -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
}
]
}

View file

@ -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"
>;