@foundrynorth/compass-schema 1.0.20 → 1.0.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/schema.js CHANGED
@@ -86,6 +86,7 @@ import { sql } from "drizzle-orm";
86
86
  import { pgTable, text, varchar, integer, jsonb, timestamp, doublePrecision, serial, numeric, uuid, date, index, boolean, pgEnum, unique, uniqueIndex, } from "drizzle-orm/pg-core";
87
87
  import { createInsertSchema } from "drizzle-zod";
88
88
  import { z } from "zod";
89
+ import { ACTIVITY_EVENT_TYPES, ACTIVITY_SEVERITIES, ACTIVITY_SOURCE_SYSTEMS, PENDING_ACTION_TYPES, } from "./types/activity-feed.js";
89
90
  export const users = pgTable("users", {
90
91
  id: varchar("id")
91
92
  .primaryKey()
@@ -98,6 +99,21 @@ export const insertUserSchema = createInsertSchema(users).pick({
98
99
  password: true,
99
100
  });
100
101
  export const avatarRequestStatusEnum = pgEnum("avatar_request_status", ["pending", "processed", "rejected"]);
102
+ // Activity feed enums (order activity feed + pending actions)
103
+ export const activityEventTypeEnum = pgEnum("activity_event_type", ACTIVITY_EVENT_TYPES);
104
+ export const activitySeverityEnum = pgEnum("activity_severity", ACTIVITY_SEVERITIES);
105
+ export const activitySourceSystemEnum = pgEnum("activity_source_system", ACTIVITY_SOURCE_SYSTEMS);
106
+ export const pendingActionTypeEnum = pgEnum("pending_action_type", PENDING_ACTION_TYPES);
107
+ export const amendmentReasonEnum = pgEnum("amendment_reason", [
108
+ "budget_adjustment",
109
+ "flight_date_change",
110
+ "product_swap",
111
+ "add_line_items",
112
+ "remove_line_items",
113
+ "rate_renegotiation",
114
+ "client_request",
115
+ "other",
116
+ ]);
101
117
  // Vendor enums (must be declared before clerkUsers which references them)
102
118
  export const vendorFulfillmentStatusEnum = pgEnum("vendor_fulfillment_status", [
103
119
  "assigned",
@@ -167,6 +183,19 @@ export const userProfiles = pgTable("user_profiles", {
167
183
  photoUrl: text("photo_url"), // Profile photo (can sync from Clerk or custom upload)
168
184
  agencyAvatarUrl: text("agency_avatar_url"), // Hand-sketched avatar for proposals (Fal.ai)
169
185
  lastAvatarGeneratedAt: timestamp("last_avatar_generated_at"), // Rate limiting: non-admins can only generate 1/day
186
+ proposalPortraitRenderUrl: text("proposal_portrait_render_url"),
187
+ proposalPortraitCutoutUrl: text("proposal_portrait_cutout_url"),
188
+ proposalPortraitStatus: text("proposal_portrait_status"),
189
+ proposalPortraitPromptVersion: text("proposal_portrait_prompt_version"),
190
+ proposalPortraitSeed: integer("proposal_portrait_seed"),
191
+ proposalPortraitGeneratedAt: timestamp("proposal_portrait_generated_at"),
192
+ proposalPortraitSourcePhotoHash: text("proposal_portrait_source_photo_hash"),
193
+ proposalPortraitPreviewRenderUrl: text("proposal_portrait_preview_render_url"),
194
+ proposalPortraitPreviewCutoutUrl: text("proposal_portrait_preview_cutout_url"),
195
+ proposalPortraitPreviewPromptVersion: text("proposal_portrait_preview_prompt_version"),
196
+ proposalPortraitPreviewSeed: integer("proposal_portrait_preview_seed"),
197
+ proposalPortraitPreviewGeneratedAt: timestamp("proposal_portrait_preview_generated_at"),
198
+ proposalPortraitPreviewSourcePhotoHash: text("proposal_portrait_preview_source_photo_hash"),
170
199
  // Active Market Preference (overrides partner default)
171
200
  preferredMarketId: varchar("preferred_market_id"), // User's preferred market for planning
172
201
  // Digital Business Card
@@ -195,6 +224,19 @@ export const externalTeamMembers = pgTable("external_team_members", {
195
224
  title: text("title"),
196
225
  photoUrl: text("photo_url"),
197
226
  agencyAvatarUrl: text("agency_avatar_url"),
227
+ proposalPortraitRenderUrl: text("proposal_portrait_render_url"),
228
+ proposalPortraitCutoutUrl: text("proposal_portrait_cutout_url"),
229
+ proposalPortraitStatus: text("proposal_portrait_status"),
230
+ proposalPortraitPromptVersion: text("proposal_portrait_prompt_version"),
231
+ proposalPortraitSeed: integer("proposal_portrait_seed"),
232
+ proposalPortraitGeneratedAt: timestamp("proposal_portrait_generated_at"),
233
+ proposalPortraitSourcePhotoHash: text("proposal_portrait_source_photo_hash"),
234
+ proposalPortraitPreviewRenderUrl: text("proposal_portrait_preview_render_url"),
235
+ proposalPortraitPreviewCutoutUrl: text("proposal_portrait_preview_cutout_url"),
236
+ proposalPortraitPreviewPromptVersion: text("proposal_portrait_preview_prompt_version"),
237
+ proposalPortraitPreviewSeed: integer("proposal_portrait_preview_seed"),
238
+ proposalPortraitPreviewGeneratedAt: timestamp("proposal_portrait_preview_generated_at"),
239
+ proposalPortraitPreviewSourcePhotoHash: text("proposal_portrait_preview_source_photo_hash"),
198
240
  linkedinUrl: text("linkedin_url"),
199
241
  displayOrder: integer("display_order").default(0),
200
242
  createdAt: timestamp("created_at").notNull().defaultNow(),
@@ -433,6 +475,30 @@ export const competitorSchema = z.object({
433
475
  lng: z.number(),
434
476
  })
435
477
  .optional(), // Detailed location information
478
+ // Ad library data from Apify deep enrichment
479
+ ads: z
480
+ .object({
481
+ facebook: z
482
+ .array(z.object({
483
+ adImage: z.string().optional(),
484
+ headline: z.string().optional(),
485
+ adCopy: z.string().optional(),
486
+ callToAction: z.string().optional(),
487
+ publisherPlatforms: z.array(z.string()).optional(),
488
+ startDate: z.string().optional(),
489
+ }))
490
+ .optional(),
491
+ google: z
492
+ .array(z.object({
493
+ headline: z.string().optional(),
494
+ description: z.string().optional(),
495
+ landingPage: z.string().optional(),
496
+ adType: z.string().optional(),
497
+ imageUrl: z.string().optional(),
498
+ }))
499
+ .optional(),
500
+ })
501
+ .optional(),
436
502
  });
437
503
  export const extractionResultSchema = z.object({
438
504
  brand: z.string(),
@@ -855,7 +921,7 @@ export const plans = pgTable("plans", {
855
921
  selectedOptionId: varchar("selected_option_id"), // FK → plan_options.id (nullable during migration)
856
922
  contractId: uuid("contract_id"), // FK → contracts.id (nullable — links to annual contract)
857
923
  // Workflow mode: determines which UI sections and features are available
858
- // 'research_backed' = full AnalyzeAndPlan flow (default, all existing plans)
924
+ // 'research_backed' = discovery mode creates plan, runs analysis in background
859
925
  // 'direct_config' = configure products directly without research
860
926
  // 'aor_pitch' = agency-of-record pitch with proposal focus
861
927
  // 'io_entry' = direct insertion order entry from client PDF/XLSX
@@ -974,6 +1040,7 @@ export const plans = pgTable("plans", {
974
1040
  defaultTargeting: text("default_targeting"),
975
1041
  defaultAudience: text("default_audience"),
976
1042
  defaultPromoting: text("default_promoting"),
1043
+ defaultCreativeSource: varchar("default_creative_source"),
977
1044
  defaultFlightStart: date("default_flight_start"),
978
1045
  defaultFlightEnd: date("default_flight_end"),
979
1046
  // Slack integration — per-plan client channel for alert routing
@@ -1027,8 +1094,12 @@ export const proposals = pgTable("proposals", {
1027
1094
  .primaryKey()
1028
1095
  .default(sql `gen_random_uuid()`),
1029
1096
  // Optional plan reference (nullable — can be standalone)
1030
- planId: varchar("plan_id"),
1031
- engagementId: varchar("engagement_id"), // FK → engagements.id (nullable during transition)
1097
+ planId: varchar("plan_id").references(() => plans.id, {
1098
+ onDelete: "set null",
1099
+ }),
1100
+ engagementId: varchar("engagement_id").references(() => engagements.id, {
1101
+ onDelete: "set null",
1102
+ }), // nullable during transition
1032
1103
  // Configuration references (which plan_configurations to include)
1033
1104
  configurationIds: jsonb("configuration_ids").$type(),
1034
1105
  optionIds: jsonb("option_ids").$type(),
@@ -1080,6 +1151,35 @@ export const insertPlanSchema = createInsertSchema(plans).omit({
1080
1151
  id: true,
1081
1152
  createdAt: true,
1082
1153
  });
1154
+ // ============================================================================
1155
+ // Proposal Engagement Tracking
1156
+ // ============================================================================
1157
+ export const proposalEngagement = pgTable("proposal_engagement", {
1158
+ id: varchar("id")
1159
+ .primaryKey()
1160
+ .default(sql `gen_random_uuid()`),
1161
+ proposalId: varchar("proposal_id"), // FK → proposals.id (nullable for legacy plan-based shares)
1162
+ planId: varchar("plan_id"), // FK → plans.id (fallback for legacy shares)
1163
+ shareToken: text("share_token").notNull(), // The share token used to access
1164
+ sessionId: text("session_id").notNull(), // Anonymous session ID (crypto.randomUUID on client)
1165
+ // Engagement data
1166
+ events: jsonb("events").notNull().default(sql `'[]'::jsonb`), // Array of engagement events
1167
+ engagementScore: integer("engagement_score").default(0), // Computed 0-100 score
1168
+ summary: text("summary"), // AI-generated follow-up brief
1169
+ // Device context
1170
+ device: text("device"), // "desktop" | "mobile" | "tablet"
1171
+ referrer: text("referrer"), // HTTP referrer
1172
+ userAgent: text("user_agent"),
1173
+ // Timestamps
1174
+ firstSeenAt: timestamp("first_seen_at").notNull().defaultNow(),
1175
+ lastSeenAt: timestamp("last_seen_at").notNull().defaultNow(),
1176
+ createdAt: timestamp("created_at").notNull().defaultNow(),
1177
+ }, (table) => [
1178
+ index("proposal_engagement_proposal_id_idx").on(table.proposalId),
1179
+ index("proposal_engagement_plan_id_idx").on(table.planId),
1180
+ index("proposal_engagement_share_token_idx").on(table.shareToken),
1181
+ index("proposal_engagement_token_session_idx").on(table.shareToken, table.sessionId),
1182
+ ]);
1083
1183
  // Plan Configurations — Named product mixes (replaces monolithic planAllocations)
1084
1184
  // A plan can have multiple configurations (e.g., "Digital Focus", "Full Mix", "Print Heavy")
1085
1185
  export const planConfigurations = pgTable("plan_configurations", {
@@ -1414,6 +1514,7 @@ export const clerkUsers = pgTable("clerk_users", {
1414
1514
  createdAt: timestamp("created_at").notNull().defaultNow(),
1415
1515
  approvedAt: timestamp("approved_at"),
1416
1516
  approvedBy: text("approved_by"), // Admin who approved
1517
+ compassVoiceWarmth: numeric("compass_voice_warmth").default("0.6"),
1417
1518
  });
1418
1519
  export const insertClerkUserSchema = createInsertSchema(clerkUsers).omit({
1419
1520
  createdAt: true,
@@ -3268,19 +3369,106 @@ export const mediaOrderStatusEnum = pgEnum("media_order_status", [
3268
3369
  "active",
3269
3370
  "completed",
3270
3371
  ]);
3372
+ export const salesInitiatives = pgTable("sales_initiatives", {
3373
+ id: uuid("id").primaryKey().defaultRandom(),
3374
+ code: varchar("code").notNull().unique(),
3375
+ name: varchar("name").notNull(),
3376
+ description: text("description"),
3377
+ hubspotValue: varchar("hubspot_value").notNull().unique(),
3378
+ displayOrder: integer("display_order").notNull().default(0),
3379
+ startsAt: date("starts_at"),
3380
+ endsAt: date("ends_at"),
3381
+ isActive: boolean("is_active").notNull().default(true),
3382
+ createdAt: timestamp("created_at").notNull().defaultNow(),
3383
+ updatedAt: timestamp("updated_at").notNull().defaultNow(),
3384
+ }, (table) => [
3385
+ index("sales_initiatives_active_idx").on(table.isActive),
3386
+ index("sales_initiatives_display_order_idx").on(table.displayOrder),
3387
+ ]);
3388
+ export const insertSalesInitiativeSchema = createInsertSchema(salesInitiatives).omit({
3389
+ id: true,
3390
+ createdAt: true,
3391
+ updatedAt: true,
3392
+ });
3393
+ export const updateSalesInitiativeSchema = insertSalesInitiativeSchema.partial();
3394
+ export const salesPackages = pgTable("sales_packages", {
3395
+ id: uuid("id").primaryKey().defaultRandom(),
3396
+ code: varchar("code").notNull().unique(),
3397
+ name: varchar("name").notNull(),
3398
+ description: text("description"),
3399
+ initiativeCode: varchar("initiative_code"),
3400
+ sellerSummary: text("seller_summary"),
3401
+ packagePrice: numeric("package_price", { precision: 12, scale: 2 }),
3402
+ discountPercent: numeric("discount_percent", { precision: 5, scale: 2 }),
3403
+ addedValueAmount: numeric("added_value_amount", { precision: 12, scale: 2 }),
3404
+ addedValueDescription: varchar("added_value_description", { length: 255 }),
3405
+ hubspotPackageValue: varchar("hubspot_package_value").notNull().unique(),
3406
+ startsAt: date("starts_at"),
3407
+ endsAt: date("ends_at"),
3408
+ status: varchar("status").notNull().default("draft"),
3409
+ recommendationMode: varchar("recommendation_mode").notNull().default("both"),
3410
+ recommendationPriority: integer("recommendation_priority").notNull().default(0),
3411
+ eligibilityRules: jsonb("eligibility_rules").$type(),
3412
+ assistantHints: jsonb("assistant_hints").$type(),
3413
+ taskTemplateMode: varchar("task_template_mode").notNull().default("standard"),
3414
+ displayOrder: integer("display_order").notNull().default(0),
3415
+ isActive: boolean("is_active").notNull().default(true),
3416
+ createdAt: timestamp("created_at").notNull().defaultNow(),
3417
+ updatedAt: timestamp("updated_at").notNull().defaultNow(),
3418
+ }, (table) => [
3419
+ index("sales_packages_active_idx").on(table.isActive),
3420
+ index("sales_packages_status_idx").on(table.status),
3421
+ index("sales_packages_display_order_idx").on(table.displayOrder),
3422
+ index("sales_packages_initiative_code_idx").on(table.initiativeCode),
3423
+ ]);
3424
+ export const insertSalesPackageSchema = createInsertSchema(salesPackages).omit({
3425
+ id: true,
3426
+ createdAt: true,
3427
+ updatedAt: true,
3428
+ });
3429
+ export const updateSalesPackageSchema = insertSalesPackageSchema.partial();
3430
+ export const salesPackageProducts = pgTable("sales_package_products", {
3431
+ id: uuid("id").primaryKey().defaultRandom(),
3432
+ salesPackageCode: varchar("sales_package_code").notNull(),
3433
+ stribProductId: uuid("strib_product_id")
3434
+ .notNull()
3435
+ .references(() => stribProducts.id, { onDelete: "cascade" }),
3436
+ displayOrder: integer("display_order").notNull().default(0),
3437
+ approvedUnitRate: numeric("approved_unit_rate", { precision: 12, scale: 4 }),
3438
+ approvedRateType: varchar("approved_rate_type"),
3439
+ allocationMode: varchar("allocation_mode").notNull().default("percent"),
3440
+ allocationValue: numeric("allocation_value", { precision: 12, scale: 4 }),
3441
+ required: boolean("required").notNull().default(true),
3442
+ defaultFulfillmentDetails: jsonb("default_fulfillment_details"),
3443
+ createdAt: timestamp("created_at").notNull().defaultNow(),
3444
+ updatedAt: timestamp("updated_at").notNull().defaultNow(),
3445
+ }, (table) => [
3446
+ uniqueIndex("sales_package_products_unique_idx").on(table.salesPackageCode, table.stribProductId),
3447
+ index("sales_package_products_code_idx").on(table.salesPackageCode),
3448
+ index("sales_package_products_product_idx").on(table.stribProductId),
3449
+ ]);
3271
3450
  export const mediaOrders = pgTable("media_orders", {
3272
3451
  id: varchar("id")
3273
3452
  .primaryKey()
3274
3453
  .default(sql `gen_random_uuid()`),
3275
- planId: varchar("plan_id"), // FK → plans.id (nullable — can exist without a plan)
3276
- engagementId: varchar("engagement_id"), // FK → engagements.id (nullable during transition)
3454
+ planId: varchar("plan_id").references(() => plans.id, {
3455
+ onDelete: "set null",
3456
+ }), // nullable — can exist without a plan
3457
+ engagementId: varchar("engagement_id").references(() => engagements.id, {
3458
+ onDelete: "set null",
3459
+ }), // nullable during transition
3277
3460
  configurationId: varchar("configuration_id"), // FK → plan_configurations.id (named product mix)
3278
3461
  optionId: varchar("option_id"), // FK → plan_options.id (canonical selected option)
3279
- contractId: uuid("contract_id"), // FK → contracts.id (nullable — links to annual contract)
3462
+ contractId: uuid("contract_id").references(() => contracts.id, {
3463
+ onDelete: "set null",
3464
+ }), // nullable — links to annual contract
3280
3465
  partnerId: varchar("partner_id"),
3281
3466
  hubspotDealId: text("hubspot_deal_id"),
3282
3467
  hubspotCompanyId: text("hubspot_company_id"),
3283
3468
  parentHubspotCompanyId: text("parent_hubspot_company_id"),
3469
+ initiativeCode: varchar("initiative_code"),
3470
+ salesPackageCode: varchar("sales_package_code"),
3471
+ salesPackageSnapshot: jsonb("sales_package_snapshot").$type(),
3284
3472
  // Client header
3285
3473
  clientName: text("client_name").notNull(),
3286
3474
  clientContactName: text("client_contact_name"),
@@ -3304,6 +3492,8 @@ export const mediaOrders = pgTable("media_orders", {
3304
3492
  activatedAt: timestamp("activated_at"),
3305
3493
  completedAt: timestamp("completed_at"),
3306
3494
  returnReason: text("return_reason"),
3495
+ amendmentReason: amendmentReasonEnum("amendment_reason"),
3496
+ amendmentNotes: text("amendment_notes"),
3307
3497
  slaHold: boolean("sla_hold").default(false),
3308
3498
  clientTier: text("client_tier"),
3309
3499
  // Share link
@@ -3329,8 +3519,6 @@ export const mediaOrders = pgTable("media_orders", {
3329
3519
  defaultPromoting: text("default_promoting"),
3330
3520
  // Order-level notes
3331
3521
  orderNotes: text("order_notes"),
3332
- // Sales initiative (links to sales_initiatives table)
3333
- initiativeCode: varchar("initiative_code"),
3334
3522
  // Billing fields
3335
3523
  navigaAdvertiserId: text("naviga_advertiser_id"),
3336
3524
  purchaseOrderNumber: text("purchase_order_number"),
@@ -3378,12 +3566,14 @@ export const mediaOrders = pgTable("media_orders", {
3378
3566
  index("media_orders_engagement_id_idx").on(table.engagementId),
3379
3567
  index("media_orders_partner_id_idx").on(table.partnerId),
3380
3568
  index("media_orders_hubspot_company_id_idx").on(table.hubspotCompanyId),
3569
+ index("media_orders_hubspot_deal_id_idx").on(table.hubspotDealId),
3381
3570
  index("media_orders_status_idx").on(table.status),
3382
3571
  index("media_orders_vendor_id_idx").on(table.vendorId),
3383
3572
  index("media_orders_vendor_fulfillment_status_idx").on(table.vendorFulfillmentStatus),
3384
3573
  index("media_orders_parent_company_idx").on(table.parentHubspotCompanyId),
3385
3574
  index("media_orders_contract_id_idx").on(table.contractId),
3386
3575
  index("media_orders_initiative_code_idx").on(table.initiativeCode),
3576
+ index("media_orders_sales_package_code_idx").on(table.salesPackageCode),
3387
3577
  ]);
3388
3578
  export const insertMediaOrderSchema = createInsertSchema(mediaOrders).omit({
3389
3579
  id: true,
@@ -3476,6 +3666,11 @@ export const mediaOrderLineItems = pgTable("media_order_line_items", {
3476
3666
  // Fulfillment details (product-specific fields for HubSpot sync)
3477
3667
  fulfillmentDetails: jsonb("fulfillment_details"),
3478
3668
  creativeSource: varchar("creative_source"),
3669
+ initiativeCode: varchar("initiative_code"),
3670
+ salesPackageCode: varchar("sales_package_code"),
3671
+ salesPackageComponentRef: varchar("sales_package_component_ref"),
3672
+ isPackageDerived: boolean("is_package_derived").notNull().default(false),
3673
+ packagePricingSource: varchar("package_pricing_source"),
3479
3674
  // Ad trafficking reference key (auto-generated naming convention)
3480
3675
  trafficKey: text("traffic_key"),
3481
3676
  // Canonical tracking fields for DSP/reporting consistency
@@ -3485,9 +3680,12 @@ export const mediaOrderLineItems = pgTable("media_order_line_items", {
3485
3680
  updatedAt: timestamp("updated_at").notNull().defaultNow(),
3486
3681
  }, (table) => [
3487
3682
  index("media_order_line_items_section_id_idx").on(table.sectionId),
3683
+ index("media_order_line_items_sync_status_idx").on(table.syncStatus),
3488
3684
  index("idx_line_items_parent").on(table.parentLineItemId),
3489
3685
  index("media_order_line_items_location_id_idx").on(table.locationId),
3490
3686
  index("media_order_line_items_tracking_key_idx").on(table.trackingKey),
3687
+ index("media_order_line_items_initiative_code_idx").on(table.initiativeCode),
3688
+ index("media_order_line_items_sales_package_code_idx").on(table.salesPackageCode),
3491
3689
  ]);
3492
3690
  export const insertMediaOrderLineItemSchema = createInsertSchema(mediaOrderLineItems).omit({
3493
3691
  id: true,
@@ -3652,6 +3850,20 @@ export const insertStribProductSchema = createInsertSchema(stribProducts).omit({
3652
3850
  updatedAt: true,
3653
3851
  });
3654
3852
  export const updateStribProductSchema = insertStribProductSchema.partial();
3853
+ export const salesInitiativeProducts = pgTable("sales_initiative_products", {
3854
+ id: uuid("id").primaryKey().defaultRandom(),
3855
+ initiativeCode: varchar("initiative_code").notNull(),
3856
+ stribProductId: uuid("strib_product_id")
3857
+ .notNull()
3858
+ .references(() => stribProducts.id, { onDelete: "cascade" }),
3859
+ displayOrder: integer("display_order").notNull().default(0),
3860
+ createdAt: timestamp("created_at").notNull().defaultNow(),
3861
+ updatedAt: timestamp("updated_at").notNull().defaultNow(),
3862
+ }, (table) => [
3863
+ uniqueIndex("sales_initiative_products_unique_idx").on(table.initiativeCode, table.stribProductId),
3864
+ index("sales_initiative_products_code_idx").on(table.initiativeCode),
3865
+ index("sales_initiative_products_product_idx").on(table.stribProductId),
3866
+ ]);
3655
3867
  /**
3656
3868
  * strib_product_rates — tier-based pricing per product.
3657
3869
  * Different tiers depending on family: Digital uses open/advocacy/tier_1/tier_2/tier_3;
@@ -3915,6 +4127,7 @@ export const rateExceptionRequests = pgTable("rate_exception_requests", {
3915
4127
  escalationHistory: jsonb("escalation_history"), // Array of {level, at, by, reason}
3916
4128
  expiresAt: timestamp("expires_at"),
3917
4129
  approvalScope: varchar("approval_scope").notNull().default("request"), // "request" = affects this exception only; "catalog" = also updates global product rate
4130
+ batchId: uuid("batch_id"), // Groups bulk rate exception requests submitted together
3918
4131
  createdAt: timestamp("created_at").notNull().defaultNow(),
3919
4132
  updatedAt: timestamp("updated_at").notNull().defaultNow(),
3920
4133
  }, (table) => [
@@ -3922,6 +4135,8 @@ export const rateExceptionRequests = pgTable("rate_exception_requests", {
3922
4135
  index("rate_exception_status_idx").on(table.status),
3923
4136
  index("rate_exception_requester_idx").on(table.requestedByUserId),
3924
4137
  index("rate_exception_plan_idx").on(table.planId),
4138
+ index("rate_exception_batch_idx").on(table.batchId),
4139
+ index("rate_exception_order_status_idx").on(table.mediaOrderId, table.status),
3925
4140
  ]);
3926
4141
  // ─── Lead Time Rules ──────────────────────────────────────────────────────────
3927
4142
  export const leadTimeRules = pgTable("lead_time_rules", {
@@ -3968,6 +4183,31 @@ export const slaConfig = pgTable("sla_config", {
3968
4183
  createdAt: timestamp("created_at").defaultNow(),
3969
4184
  updatedAt: timestamp("updated_at").defaultNow(),
3970
4185
  });
4186
+ // ─── SLA Snapshots ───────────────────────────────────────────────────────────
4187
+ /**
4188
+ * sla_snapshots — Point-in-time SLA state from fn-flux evaluations.
4189
+ * Each row captures the hold status, deadline, milestones, and profile
4190
+ * for a given media order + fulfillment ticket pair.
4191
+ */
4192
+ export const slaHoldStatusEnum = pgEnum("sla_hold_status", ["clear", "hold", "override"]);
4193
+ export const slaSnapshots = pgTable("sla_snapshots", {
4194
+ id: serial("id").primaryKey(),
4195
+ mediaOrderId: text("media_order_id").notNull().references(() => mediaOrders.id),
4196
+ ticketId: text("ticket_id").notNull(),
4197
+ holdStatus: slaHoldStatusEnum("hold_status").notNull().default("clear"),
4198
+ holdReason: text("hold_reason"),
4199
+ slaDays: integer("sla_days").notNull(),
4200
+ availableDays: numeric("available_days"),
4201
+ slaDeadline: timestamp("sla_deadline"),
4202
+ campaignStartDate: date("campaign_start_date"),
4203
+ milestones: jsonb("milestones"),
4204
+ profileName: text("profile_name"),
4205
+ lastUpdatedAt: timestamp("last_updated_at").defaultNow(),
4206
+ }, (table) => [
4207
+ index("sla_snapshots_media_order_id_idx").on(table.mediaOrderId),
4208
+ index("sla_snapshots_ticket_id_idx").on(table.ticketId),
4209
+ uniqueIndex("sla_snapshots_order_ticket_uniq").on(table.mediaOrderId, table.ticketId),
4210
+ ]);
3971
4211
  // ─── Inventory Sync Audit Trail ─────────────────────────────────────────────
3972
4212
  /**
3973
4213
  * inventory_sync_log — Audit trail for external inventory syncs (GAM, Sailthru, Naviga).
@@ -5932,6 +6172,7 @@ export const semCampaigns = pgTable("sem_campaigns", {
5932
6172
  // =============================================================================
5933
6173
  export const compassEventOutboxStatusEnum = pgEnum("compass_event_outbox_status", [
5934
6174
  "pending",
6175
+ "processing",
5935
6176
  "failed",
5936
6177
  "delivered",
5937
6178
  "dead_letter",
@@ -5967,4 +6208,73 @@ export const creativeDeliveries = pgTable("creative_deliveries", {
5967
6208
  assetCount: integer("asset_count").notNull().default(0),
5968
6209
  receivedAt: timestamp("received_at").defaultNow().notNull(),
5969
6210
  });
6211
+ // ─── Order Activity Feed ────────────────────────────────────────────────────
6212
+ export const orderActivityFeed = pgTable("order_activity_feed", {
6213
+ id: uuid("id").primaryKey().defaultRandom(),
6214
+ mediaOrderId: varchar("media_order_id")
6215
+ .notNull()
6216
+ .references(() => mediaOrders.id),
6217
+ lineItemId: varchar("line_item_id").references(() => mediaOrderLineItems.id),
6218
+ eventType: activityEventTypeEnum("event_type").notNull(),
6219
+ title: text("title").notNull(),
6220
+ detail: text("detail"),
6221
+ severity: activitySeverityEnum("severity").notNull(),
6222
+ sourceSystem: activitySourceSystemEnum("source_system").notNull(),
6223
+ externalId: text("external_id").notNull().unique(), // idempotency key — prevents duplicate event ingestion
6224
+ status: text("status"), // intentionally untyped — values vary by eventType (creative vs fulfillment vs contract)
6225
+ occurredAt: timestamp("occurred_at").notNull(),
6226
+ createdAt: timestamp("created_at").notNull().defaultNow(),
6227
+ metadata: jsonb("metadata").$type(),
6228
+ }, (table) => [
6229
+ index("order_activity_feed_order_occurred_idx").on(table.mediaOrderId, table.occurredAt),
6230
+ index("order_activity_feed_line_item_idx").on(table.lineItemId),
6231
+ ]);
6232
+ // ─── Pending Actions ────────────────────────────────────────────────────────
6233
+ export const pendingActions = pgTable("pending_actions", {
6234
+ id: uuid("id").primaryKey().defaultRandom(),
6235
+ mediaOrderId: varchar("media_order_id")
6236
+ .notNull()
6237
+ .references(() => mediaOrders.id),
6238
+ lineItemId: varchar("line_item_id").references(() => mediaOrderLineItems.id),
6239
+ actionType: pendingActionTypeEnum("action_type").notNull(),
6240
+ message: text("message").notNull(),
6241
+ activityFeedId: uuid("activity_feed_id")
6242
+ .notNull()
6243
+ .references(() => orderActivityFeed.id),
6244
+ resolvedAt: timestamp("resolved_at"),
6245
+ createdAt: timestamp("created_at").notNull().defaultNow(),
6246
+ }, (table) => [
6247
+ index("pending_actions_order_idx").on(table.mediaOrderId),
6248
+ index("pending_actions_line_item_idx").on(table.lineItemId),
6249
+ index("pending_actions_activity_feed_idx").on(table.activityFeedId),
6250
+ index("pending_actions_unresolved_idx").on(table.mediaOrderId).where(sql `resolved_at IS NULL`),
6251
+ ]);
6252
+ // ─── Order Version Snapshots ────────────────────────────────────────────────
6253
+ export const orderVersionSnapshots = pgTable("order_version_snapshots", {
6254
+ id: uuid("id").primaryKey().defaultRandom(),
6255
+ mediaOrderId: varchar("media_order_id")
6256
+ .notNull()
6257
+ .references(() => mediaOrders.id),
6258
+ version: integer("version").notNull(),
6259
+ amendmentReason: text("amendment_reason"),
6260
+ amendmentNotes: text("amendment_notes"),
6261
+ amendedBy: varchar("amended_by"),
6262
+ snapshot: jsonb("snapshot").notNull().$type(),
6263
+ netInvestment: numeric("net_investment", { precision: 12, scale: 2 }),
6264
+ lineItemCount: integer("line_item_count"),
6265
+ createdAt: timestamp("created_at").notNull().defaultNow(),
6266
+ }, (table) => [
6267
+ index("order_version_snapshots_order_version_idx").on(table.mediaOrderId, table.version),
6268
+ ]);
6269
+ // ─── User Milestones ────────────────────────────────────────────────────────
6270
+ export const userMilestones = pgTable("user_milestones", {
6271
+ id: uuid("id").primaryKey().defaultRandom(),
6272
+ userId: varchar("user_id").notNull(),
6273
+ milestoneKey: text("milestone_key").notNull(),
6274
+ shownAt: timestamp("shown_at").notNull().defaultNow(),
6275
+ createdAt: timestamp("created_at").notNull().defaultNow(),
6276
+ }, (table) => [
6277
+ index("user_milestones_user_id_idx").on(table.userId),
6278
+ unique("user_milestones_user_key_unique").on(table.userId, table.milestoneKey),
6279
+ ]);
5970
6280
  //# sourceMappingURL=schema.js.map