@foundrynorth/compass-schema 1.0.20 → 1.0.21
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/LICENSE +4 -3
- package/dist/schema.d.ts +2039 -47
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +182 -3
- package/dist/schema.js.map +1 -1
- package/dist/types/activity-feed.d.ts +37 -0
- package/dist/types/activity-feed.d.ts.map +1 -0
- package/dist/types/activity-feed.js +63 -0
- package/dist/types/activity-feed.js.map +1 -0
- package/package.json +2 -2
- package/src/schema.ts +296 -4
- package/src/types/activity-feed.ts +97 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types for the order activity feed — the backbone for all
|
|
3
|
+
* post-submission visibility (contract alerts, fulfillment status,
|
|
4
|
+
* creative status, amendments, rate exception resolution).
|
|
5
|
+
*/
|
|
6
|
+
export declare const ACTIVITY_EVENT_TYPES: readonly ["fulfillment_stage_changed", "creative_status_changed", "creative_delivered", "signing_progress", "drawdown_alert", "renewal_reminder", "amendment_submitted", "rate_exception_resolved", "sla_hold_set", "sla_hold_cleared", "sla_milestone_at_risk", "sla_milestone_breached", "sla_evaluated"];
|
|
7
|
+
export type ActivityEventType = (typeof ACTIVITY_EVENT_TYPES)[number];
|
|
8
|
+
export declare const ACTIVITY_SEVERITIES: readonly ["info", "success", "warning", "action_needed"];
|
|
9
|
+
export type ActivitySeverity = (typeof ACTIVITY_SEVERITIES)[number];
|
|
10
|
+
export declare const ACTIVITY_SOURCE_SYSTEMS: readonly ["flux", "forge", "hubspot", "compass"];
|
|
11
|
+
export type ActivitySourceSystem = (typeof ACTIVITY_SOURCE_SYSTEMS)[number];
|
|
12
|
+
export declare const PENDING_ACTION_TYPES: readonly ["creative_revision_needed", "creative_rejected", "fulfillment_blocked", "rate_exception_pending", "sla_hold_active"];
|
|
13
|
+
export type PendingActionType = (typeof PENDING_ACTION_TYPES)[number];
|
|
14
|
+
/** The event envelope fn-flux sends to fn-legacy */
|
|
15
|
+
export interface CompassStatusEvent {
|
|
16
|
+
schemaVersion: "v1";
|
|
17
|
+
eventId: string;
|
|
18
|
+
eventType: string;
|
|
19
|
+
idempotencyKey: string;
|
|
20
|
+
emittedAt: string;
|
|
21
|
+
payload: {
|
|
22
|
+
mediaOrderId: string;
|
|
23
|
+
lineItemId?: string | null;
|
|
24
|
+
hubspotCompanyId?: string | null;
|
|
25
|
+
title: string;
|
|
26
|
+
detail?: string | null;
|
|
27
|
+
severity: ActivitySeverity;
|
|
28
|
+
status?: string | null;
|
|
29
|
+
metadata?: Record<string, unknown> | null;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/** Resolution mapping: which events clear which pending actions */
|
|
33
|
+
export declare const PENDING_ACTION_RESOLUTION_MAP: Record<PendingActionType, {
|
|
34
|
+
resolvedByEventType: ActivityEventType;
|
|
35
|
+
resolvedByStatuses: string[];
|
|
36
|
+
}>;
|
|
37
|
+
//# sourceMappingURL=activity-feed.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"activity-feed.d.ts","sourceRoot":"","sources":["../../src/types/activity-feed.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,eAAO,MAAM,oBAAoB,6SAcvB,CAAC;AAEX,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEtE,eAAO,MAAM,mBAAmB,0DAKtB,CAAC;AAEX,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEpE,eAAO,MAAM,uBAAuB,kDAK1B,CAAC;AAEX,MAAM,MAAM,oBAAoB,GAAG,CAAC,OAAO,uBAAuB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE5E,eAAO,MAAM,oBAAoB,gIAMvB,CAAC;AAEX,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEtE,oDAAoD;AACpD,MAAM,WAAW,kBAAkB;IACjC,aAAa,EAAE,IAAI,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE;QACP,YAAY,EAAE,MAAM,CAAC;QACrB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC3B,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACjC,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACvB,QAAQ,EAAE,gBAAgB,CAAC;QAC3B,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACvB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;KAC3C,CAAC;CACH;AAED,mEAAmE;AACnE,eAAO,MAAM,6BAA6B,EAAE,MAAM,CAChD,iBAAiB,EACjB;IAAE,mBAAmB,EAAE,iBAAiB,CAAC;IAAC,kBAAkB,EAAE,MAAM,EAAE,CAAA;CAAE,CAsBzE,CAAC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types for the order activity feed — the backbone for all
|
|
3
|
+
* post-submission visibility (contract alerts, fulfillment status,
|
|
4
|
+
* creative status, amendments, rate exception resolution).
|
|
5
|
+
*/
|
|
6
|
+
export const ACTIVITY_EVENT_TYPES = [
|
|
7
|
+
"fulfillment_stage_changed",
|
|
8
|
+
"creative_status_changed",
|
|
9
|
+
"creative_delivered",
|
|
10
|
+
"signing_progress",
|
|
11
|
+
"drawdown_alert",
|
|
12
|
+
"renewal_reminder",
|
|
13
|
+
"amendment_submitted",
|
|
14
|
+
"rate_exception_resolved",
|
|
15
|
+
"sla_hold_set",
|
|
16
|
+
"sla_hold_cleared",
|
|
17
|
+
"sla_milestone_at_risk",
|
|
18
|
+
"sla_milestone_breached",
|
|
19
|
+
"sla_evaluated",
|
|
20
|
+
];
|
|
21
|
+
export const ACTIVITY_SEVERITIES = [
|
|
22
|
+
"info",
|
|
23
|
+
"success",
|
|
24
|
+
"warning",
|
|
25
|
+
"action_needed",
|
|
26
|
+
];
|
|
27
|
+
export const ACTIVITY_SOURCE_SYSTEMS = [
|
|
28
|
+
"flux",
|
|
29
|
+
"forge",
|
|
30
|
+
"hubspot",
|
|
31
|
+
"compass",
|
|
32
|
+
];
|
|
33
|
+
export const PENDING_ACTION_TYPES = [
|
|
34
|
+
"creative_revision_needed",
|
|
35
|
+
"creative_rejected",
|
|
36
|
+
"fulfillment_blocked",
|
|
37
|
+
"rate_exception_pending",
|
|
38
|
+
"sla_hold_active",
|
|
39
|
+
];
|
|
40
|
+
/** Resolution mapping: which events clear which pending actions */
|
|
41
|
+
export const PENDING_ACTION_RESOLUTION_MAP = {
|
|
42
|
+
creative_revision_needed: {
|
|
43
|
+
resolvedByEventType: "creative_status_changed",
|
|
44
|
+
resolvedByStatuses: ["approved", "completed"],
|
|
45
|
+
},
|
|
46
|
+
creative_rejected: {
|
|
47
|
+
resolvedByEventType: "creative_status_changed",
|
|
48
|
+
resolvedByStatuses: ["approved"],
|
|
49
|
+
},
|
|
50
|
+
fulfillment_blocked: {
|
|
51
|
+
resolvedByEventType: "fulfillment_stage_changed",
|
|
52
|
+
resolvedByStatuses: ["pending_fulfillment", "running", "completed"],
|
|
53
|
+
},
|
|
54
|
+
rate_exception_pending: {
|
|
55
|
+
resolvedByEventType: "rate_exception_resolved",
|
|
56
|
+
resolvedByStatuses: ["approved", "auto_approved", "rejected"],
|
|
57
|
+
},
|
|
58
|
+
sla_hold_active: {
|
|
59
|
+
resolvedByEventType: "sla_hold_cleared",
|
|
60
|
+
resolvedByStatuses: ["clear"],
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
//# sourceMappingURL=activity-feed.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"activity-feed.js","sourceRoot":"","sources":["../../src/types/activity-feed.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,2BAA2B;IAC3B,yBAAyB;IACzB,oBAAoB;IACpB,kBAAkB;IAClB,gBAAgB;IAChB,kBAAkB;IAClB,qBAAqB;IACrB,yBAAyB;IACzB,cAAc;IACd,kBAAkB;IAClB,uBAAuB;IACvB,wBAAwB;IACxB,eAAe;CACP,CAAC;AAIX,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC,MAAM;IACN,SAAS;IACT,SAAS;IACT,eAAe;CACP,CAAC;AAIX,MAAM,CAAC,MAAM,uBAAuB,GAAG;IACrC,MAAM;IACN,OAAO;IACP,SAAS;IACT,SAAS;CACD,CAAC;AAIX,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,0BAA0B;IAC1B,mBAAmB;IACnB,qBAAqB;IACrB,wBAAwB;IACxB,iBAAiB;CACT,CAAC;AAuBX,mEAAmE;AACnE,MAAM,CAAC,MAAM,6BAA6B,GAGtC;IACF,wBAAwB,EAAE;QACxB,mBAAmB,EAAE,yBAAyB;QAC9C,kBAAkB,EAAE,CAAC,UAAU,EAAE,WAAW,CAAC;KAC9C;IACD,iBAAiB,EAAE;QACjB,mBAAmB,EAAE,yBAAyB;QAC9C,kBAAkB,EAAE,CAAC,UAAU,CAAC;KACjC;IACD,mBAAmB,EAAE;QACnB,mBAAmB,EAAE,2BAA2B;QAChD,kBAAkB,EAAE,CAAC,qBAAqB,EAAE,SAAS,EAAE,WAAW,CAAC;KACpE;IACD,sBAAsB,EAAE;QACtB,mBAAmB,EAAE,yBAAyB;QAC9C,kBAAkB,EAAE,CAAC,UAAU,EAAE,eAAe,EAAE,UAAU,CAAC;KAC9D;IACD,eAAe,EAAE;QACf,mBAAmB,EAAE,kBAAkB;QACvC,kBAAkB,EAAE,CAAC,OAAO,CAAC;KAC9B;CACF,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@foundrynorth/compass-schema",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.21",
|
|
4
4
|
"description": "Canonical Drizzle ORM schema for Foundry Compass (rough-waterfall database)",
|
|
5
|
-
"license": "UNLICENSED",
|
|
6
5
|
"type": "module",
|
|
7
6
|
"main": "dist/index.js",
|
|
8
7
|
"types": "dist/index.d.ts",
|
|
@@ -21,6 +20,7 @@
|
|
|
21
20
|
"src"
|
|
22
21
|
],
|
|
23
22
|
"scripts": {
|
|
23
|
+
"check": "tsc --noEmit",
|
|
24
24
|
"build": "tsc",
|
|
25
25
|
"prepublishOnly": "npm run build"
|
|
26
26
|
},
|
package/src/schema.ts
CHANGED
|
@@ -105,6 +105,12 @@ import {
|
|
|
105
105
|
import { createInsertSchema } from "drizzle-zod";
|
|
106
106
|
import { z } from "zod";
|
|
107
107
|
import type { IntelligenceData } from "./analyzeTypes.js";
|
|
108
|
+
import {
|
|
109
|
+
ACTIVITY_EVENT_TYPES,
|
|
110
|
+
ACTIVITY_SEVERITIES,
|
|
111
|
+
ACTIVITY_SOURCE_SYSTEMS,
|
|
112
|
+
PENDING_ACTION_TYPES,
|
|
113
|
+
} from "./types/activity-feed.js";
|
|
108
114
|
|
|
109
115
|
export const users = pgTable("users", {
|
|
110
116
|
id: varchar("id")
|
|
@@ -124,6 +130,38 @@ export type User = typeof users.$inferSelect;
|
|
|
124
130
|
|
|
125
131
|
export const avatarRequestStatusEnum = pgEnum("avatar_request_status", ["pending", "processed", "rejected"]);
|
|
126
132
|
|
|
133
|
+
// Activity feed enums (order activity feed + pending actions)
|
|
134
|
+
export const activityEventTypeEnum = pgEnum(
|
|
135
|
+
"activity_event_type",
|
|
136
|
+
ACTIVITY_EVENT_TYPES as unknown as [string, ...string[]],
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
export const activitySeverityEnum = pgEnum(
|
|
140
|
+
"activity_severity",
|
|
141
|
+
ACTIVITY_SEVERITIES as unknown as [string, ...string[]],
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
export const activitySourceSystemEnum = pgEnum(
|
|
145
|
+
"activity_source_system",
|
|
146
|
+
ACTIVITY_SOURCE_SYSTEMS as unknown as [string, ...string[]],
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
export const pendingActionTypeEnum = pgEnum(
|
|
150
|
+
"pending_action_type",
|
|
151
|
+
PENDING_ACTION_TYPES as unknown as [string, ...string[]],
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
export const amendmentReasonEnum = pgEnum("amendment_reason", [
|
|
155
|
+
"budget_adjustment",
|
|
156
|
+
"flight_date_change",
|
|
157
|
+
"product_swap",
|
|
158
|
+
"add_line_items",
|
|
159
|
+
"remove_line_items",
|
|
160
|
+
"rate_renegotiation",
|
|
161
|
+
"client_request",
|
|
162
|
+
"other",
|
|
163
|
+
]);
|
|
164
|
+
|
|
127
165
|
// Vendor enums (must be declared before clerkUsers which references them)
|
|
128
166
|
export const vendorFulfillmentStatusEnum = pgEnum("vendor_fulfillment_status", [
|
|
129
167
|
"assigned",
|
|
@@ -208,6 +246,19 @@ export const userProfiles = pgTable("user_profiles", {
|
|
|
208
246
|
photoUrl: text("photo_url"), // Profile photo (can sync from Clerk or custom upload)
|
|
209
247
|
agencyAvatarUrl: text("agency_avatar_url"), // Hand-sketched avatar for proposals (Fal.ai)
|
|
210
248
|
lastAvatarGeneratedAt: timestamp("last_avatar_generated_at"), // Rate limiting: non-admins can only generate 1/day
|
|
249
|
+
proposalPortraitRenderUrl: text("proposal_portrait_render_url"),
|
|
250
|
+
proposalPortraitCutoutUrl: text("proposal_portrait_cutout_url"),
|
|
251
|
+
proposalPortraitStatus: text("proposal_portrait_status"),
|
|
252
|
+
proposalPortraitPromptVersion: text("proposal_portrait_prompt_version"),
|
|
253
|
+
proposalPortraitSeed: integer("proposal_portrait_seed"),
|
|
254
|
+
proposalPortraitGeneratedAt: timestamp("proposal_portrait_generated_at"),
|
|
255
|
+
proposalPortraitSourcePhotoHash: text("proposal_portrait_source_photo_hash"),
|
|
256
|
+
proposalPortraitPreviewRenderUrl: text("proposal_portrait_preview_render_url"),
|
|
257
|
+
proposalPortraitPreviewCutoutUrl: text("proposal_portrait_preview_cutout_url"),
|
|
258
|
+
proposalPortraitPreviewPromptVersion: text("proposal_portrait_preview_prompt_version"),
|
|
259
|
+
proposalPortraitPreviewSeed: integer("proposal_portrait_preview_seed"),
|
|
260
|
+
proposalPortraitPreviewGeneratedAt: timestamp("proposal_portrait_preview_generated_at"),
|
|
261
|
+
proposalPortraitPreviewSourcePhotoHash: text("proposal_portrait_preview_source_photo_hash"),
|
|
211
262
|
|
|
212
263
|
// Active Market Preference (overrides partner default)
|
|
213
264
|
preferredMarketId: varchar("preferred_market_id"), // User's preferred market for planning
|
|
@@ -247,6 +298,19 @@ export const externalTeamMembers = pgTable("external_team_members", {
|
|
|
247
298
|
title: text("title"),
|
|
248
299
|
photoUrl: text("photo_url"),
|
|
249
300
|
agencyAvatarUrl: text("agency_avatar_url"),
|
|
301
|
+
proposalPortraitRenderUrl: text("proposal_portrait_render_url"),
|
|
302
|
+
proposalPortraitCutoutUrl: text("proposal_portrait_cutout_url"),
|
|
303
|
+
proposalPortraitStatus: text("proposal_portrait_status"),
|
|
304
|
+
proposalPortraitPromptVersion: text("proposal_portrait_prompt_version"),
|
|
305
|
+
proposalPortraitSeed: integer("proposal_portrait_seed"),
|
|
306
|
+
proposalPortraitGeneratedAt: timestamp("proposal_portrait_generated_at"),
|
|
307
|
+
proposalPortraitSourcePhotoHash: text("proposal_portrait_source_photo_hash"),
|
|
308
|
+
proposalPortraitPreviewRenderUrl: text("proposal_portrait_preview_render_url"),
|
|
309
|
+
proposalPortraitPreviewCutoutUrl: text("proposal_portrait_preview_cutout_url"),
|
|
310
|
+
proposalPortraitPreviewPromptVersion: text("proposal_portrait_preview_prompt_version"),
|
|
311
|
+
proposalPortraitPreviewSeed: integer("proposal_portrait_preview_seed"),
|
|
312
|
+
proposalPortraitPreviewGeneratedAt: timestamp("proposal_portrait_preview_generated_at"),
|
|
313
|
+
proposalPortraitPreviewSourcePhotoHash: text("proposal_portrait_preview_source_photo_hash"),
|
|
250
314
|
linkedinUrl: text("linkedin_url"),
|
|
251
315
|
displayOrder: integer("display_order").default(0),
|
|
252
316
|
createdAt: timestamp("created_at").notNull().defaultNow(),
|
|
@@ -1029,7 +1093,7 @@ export const plans = pgTable("plans", {
|
|
|
1029
1093
|
contractId: uuid("contract_id"), // FK → contracts.id (nullable — links to annual contract)
|
|
1030
1094
|
|
|
1031
1095
|
// Workflow mode: determines which UI sections and features are available
|
|
1032
|
-
// 'research_backed' =
|
|
1096
|
+
// 'research_backed' = discovery mode — creates plan, runs analysis in background
|
|
1033
1097
|
// 'direct_config' = configure products directly without research
|
|
1034
1098
|
// 'aor_pitch' = agency-of-record pitch with proposal focus
|
|
1035
1099
|
// 'io_entry' = direct insertion order entry from client PDF/XLSX
|
|
@@ -1762,6 +1826,7 @@ export const clerkUsers = pgTable("clerk_users", {
|
|
|
1762
1826
|
createdAt: timestamp("created_at").notNull().defaultNow(),
|
|
1763
1827
|
approvedAt: timestamp("approved_at"),
|
|
1764
1828
|
approvedBy: text("approved_by"), // Admin who approved
|
|
1829
|
+
compassVoiceWarmth: numeric("compass_voice_warmth").default("0.6"),
|
|
1765
1830
|
});
|
|
1766
1831
|
|
|
1767
1832
|
export const insertClerkUserSchema = createInsertSchema(clerkUsers).omit({
|
|
@@ -4707,6 +4772,39 @@ export const mediaOrderStatusEnum = pgEnum("media_order_status", [
|
|
|
4707
4772
|
"completed",
|
|
4708
4773
|
]);
|
|
4709
4774
|
|
|
4775
|
+
export const salesInitiatives = pgTable(
|
|
4776
|
+
"sales_initiatives",
|
|
4777
|
+
{
|
|
4778
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
4779
|
+
code: varchar("code").notNull().unique(),
|
|
4780
|
+
name: varchar("name").notNull(),
|
|
4781
|
+
description: text("description"),
|
|
4782
|
+
hubspotValue: varchar("hubspot_value").notNull().unique(),
|
|
4783
|
+
displayOrder: integer("display_order").notNull().default(0),
|
|
4784
|
+
startsAt: date("starts_at"),
|
|
4785
|
+
endsAt: date("ends_at"),
|
|
4786
|
+
isActive: boolean("is_active").notNull().default(true),
|
|
4787
|
+
createdAt: timestamp("created_at").notNull().defaultNow(),
|
|
4788
|
+
updatedAt: timestamp("updated_at").notNull().defaultNow(),
|
|
4789
|
+
},
|
|
4790
|
+
(table) => [
|
|
4791
|
+
index("sales_initiatives_active_idx").on(table.isActive),
|
|
4792
|
+
index("sales_initiatives_display_order_idx").on(table.displayOrder),
|
|
4793
|
+
],
|
|
4794
|
+
);
|
|
4795
|
+
|
|
4796
|
+
export const insertSalesInitiativeSchema = createInsertSchema(
|
|
4797
|
+
salesInitiatives,
|
|
4798
|
+
).omit({
|
|
4799
|
+
id: true,
|
|
4800
|
+
createdAt: true,
|
|
4801
|
+
updatedAt: true,
|
|
4802
|
+
});
|
|
4803
|
+
export const updateSalesInitiativeSchema = insertSalesInitiativeSchema.partial();
|
|
4804
|
+
export type SalesInitiative = typeof salesInitiatives.$inferSelect;
|
|
4805
|
+
export type InsertSalesInitiative = z.infer<typeof insertSalesInitiativeSchema>;
|
|
4806
|
+
export type UpdateSalesInitiative = z.infer<typeof updateSalesInitiativeSchema>;
|
|
4807
|
+
|
|
4710
4808
|
export const mediaOrders = pgTable(
|
|
4711
4809
|
"media_orders",
|
|
4712
4810
|
{
|
|
@@ -4722,6 +4820,7 @@ export const mediaOrders = pgTable(
|
|
|
4722
4820
|
hubspotDealId: text("hubspot_deal_id"),
|
|
4723
4821
|
hubspotCompanyId: text("hubspot_company_id"),
|
|
4724
4822
|
parentHubspotCompanyId: text("parent_hubspot_company_id"),
|
|
4823
|
+
initiativeCode: varchar("initiative_code"),
|
|
4725
4824
|
|
|
4726
4825
|
// Client header
|
|
4727
4826
|
clientName: text("client_name").notNull(),
|
|
@@ -4749,6 +4848,8 @@ export const mediaOrders = pgTable(
|
|
|
4749
4848
|
activatedAt: timestamp("activated_at"),
|
|
4750
4849
|
completedAt: timestamp("completed_at"),
|
|
4751
4850
|
returnReason: text("return_reason"),
|
|
4851
|
+
amendmentReason: amendmentReasonEnum("amendment_reason"),
|
|
4852
|
+
amendmentNotes: text("amendment_notes"),
|
|
4752
4853
|
slaHold: boolean("sla_hold").default(false),
|
|
4753
4854
|
clientTier: text("client_tier"),
|
|
4754
4855
|
|
|
@@ -4780,9 +4881,6 @@ export const mediaOrders = pgTable(
|
|
|
4780
4881
|
// Order-level notes
|
|
4781
4882
|
orderNotes: text("order_notes"),
|
|
4782
4883
|
|
|
4783
|
-
// Sales initiative (links to sales_initiatives table)
|
|
4784
|
-
initiativeCode: varchar("initiative_code"),
|
|
4785
|
-
|
|
4786
4884
|
// Billing fields
|
|
4787
4885
|
navigaAdvertiserId: text("naviga_advertiser_id"),
|
|
4788
4886
|
purchaseOrderNumber: text("purchase_order_number"),
|
|
@@ -4839,6 +4937,7 @@ export const mediaOrders = pgTable(
|
|
|
4839
4937
|
index("media_orders_engagement_id_idx").on(table.engagementId),
|
|
4840
4938
|
index("media_orders_partner_id_idx").on(table.partnerId),
|
|
4841
4939
|
index("media_orders_hubspot_company_id_idx").on(table.hubspotCompanyId),
|
|
4940
|
+
index("media_orders_hubspot_deal_id_idx").on(table.hubspotDealId),
|
|
4842
4941
|
index("media_orders_status_idx").on(table.status),
|
|
4843
4942
|
index("media_orders_vendor_id_idx").on(table.vendorId),
|
|
4844
4943
|
index("media_orders_vendor_fulfillment_status_idx").on(table.vendorFulfillmentStatus),
|
|
@@ -4973,6 +5072,7 @@ export const mediaOrderLineItems = pgTable(
|
|
|
4973
5072
|
// Fulfillment details (product-specific fields for HubSpot sync)
|
|
4974
5073
|
fulfillmentDetails: jsonb("fulfillment_details"),
|
|
4975
5074
|
creativeSource: varchar("creative_source"),
|
|
5075
|
+
initiativeCode: varchar("initiative_code"),
|
|
4976
5076
|
|
|
4977
5077
|
// Ad trafficking reference key (auto-generated naming convention)
|
|
4978
5078
|
trafficKey: text("traffic_key"),
|
|
@@ -4994,9 +5094,11 @@ export const mediaOrderLineItems = pgTable(
|
|
|
4994
5094
|
},
|
|
4995
5095
|
(table) => [
|
|
4996
5096
|
index("media_order_line_items_section_id_idx").on(table.sectionId),
|
|
5097
|
+
index("media_order_line_items_sync_status_idx").on(table.syncStatus),
|
|
4997
5098
|
index("idx_line_items_parent").on(table.parentLineItemId),
|
|
4998
5099
|
index("media_order_line_items_location_id_idx").on(table.locationId),
|
|
4999
5100
|
index("media_order_line_items_tracking_key_idx").on(table.trackingKey),
|
|
5101
|
+
index("media_order_line_items_initiative_code_idx").on(table.initiativeCode),
|
|
5000
5102
|
]
|
|
5001
5103
|
);
|
|
5002
5104
|
|
|
@@ -5217,6 +5319,30 @@ export const updateStribProductSchema = insertStribProductSchema.partial();
|
|
|
5217
5319
|
export type StribProduct = typeof stribProducts.$inferSelect;
|
|
5218
5320
|
export type InsertStribProduct = z.infer<typeof insertStribProductSchema>;
|
|
5219
5321
|
|
|
5322
|
+
export const salesInitiativeProducts = pgTable(
|
|
5323
|
+
"sales_initiative_products",
|
|
5324
|
+
{
|
|
5325
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
5326
|
+
initiativeCode: varchar("initiative_code").notNull(),
|
|
5327
|
+
stribProductId: uuid("strib_product_id")
|
|
5328
|
+
.notNull()
|
|
5329
|
+
.references(() => stribProducts.id, { onDelete: "cascade" }),
|
|
5330
|
+
displayOrder: integer("display_order").notNull().default(0),
|
|
5331
|
+
createdAt: timestamp("created_at").notNull().defaultNow(),
|
|
5332
|
+
updatedAt: timestamp("updated_at").notNull().defaultNow(),
|
|
5333
|
+
},
|
|
5334
|
+
(table) => [
|
|
5335
|
+
uniqueIndex("sales_initiative_products_unique_idx").on(
|
|
5336
|
+
table.initiativeCode,
|
|
5337
|
+
table.stribProductId,
|
|
5338
|
+
),
|
|
5339
|
+
index("sales_initiative_products_code_idx").on(table.initiativeCode),
|
|
5340
|
+
index("sales_initiative_products_product_idx").on(table.stribProductId),
|
|
5341
|
+
],
|
|
5342
|
+
);
|
|
5343
|
+
|
|
5344
|
+
export type SalesInitiativeProduct = typeof salesInitiativeProducts.$inferSelect;
|
|
5345
|
+
|
|
5220
5346
|
/**
|
|
5221
5347
|
* strib_product_rates — tier-based pricing per product.
|
|
5222
5348
|
* Different tiers depending on family: Digital uses open/advocacy/tier_1/tier_2/tier_3;
|
|
@@ -5555,6 +5681,7 @@ export const rateExceptionRequests = pgTable(
|
|
|
5555
5681
|
escalationHistory: jsonb("escalation_history"), // Array of {level, at, by, reason}
|
|
5556
5682
|
expiresAt: timestamp("expires_at"),
|
|
5557
5683
|
approvalScope: varchar("approval_scope").notNull().default("request"), // "request" = affects this exception only; "catalog" = also updates global product rate
|
|
5684
|
+
batchId: uuid("batch_id"), // Groups bulk rate exception requests submitted together
|
|
5558
5685
|
createdAt: timestamp("created_at").notNull().defaultNow(),
|
|
5559
5686
|
updatedAt: timestamp("updated_at").notNull().defaultNow(),
|
|
5560
5687
|
},
|
|
@@ -5563,6 +5690,8 @@ export const rateExceptionRequests = pgTable(
|
|
|
5563
5690
|
index("rate_exception_status_idx").on(table.status),
|
|
5564
5691
|
index("rate_exception_requester_idx").on(table.requestedByUserId),
|
|
5565
5692
|
index("rate_exception_plan_idx").on(table.planId),
|
|
5693
|
+
index("rate_exception_batch_idx").on(table.batchId),
|
|
5694
|
+
index("rate_exception_order_status_idx").on(table.mediaOrderId, table.status),
|
|
5566
5695
|
]
|
|
5567
5696
|
);
|
|
5568
5697
|
|
|
@@ -5633,6 +5762,41 @@ export const slaConfig = pgTable("sla_config", {
|
|
|
5633
5762
|
export type SlaConfig = typeof slaConfig.$inferSelect;
|
|
5634
5763
|
export type InsertSlaConfig = typeof slaConfig.$inferInsert;
|
|
5635
5764
|
|
|
5765
|
+
// ─── SLA Snapshots ───────────────────────────────────────────────────────────
|
|
5766
|
+
|
|
5767
|
+
/**
|
|
5768
|
+
* sla_snapshots — Point-in-time SLA state from fn-flux evaluations.
|
|
5769
|
+
* Each row captures the hold status, deadline, milestones, and profile
|
|
5770
|
+
* for a given media order + fulfillment ticket pair.
|
|
5771
|
+
*/
|
|
5772
|
+
export const slaHoldStatusEnum = pgEnum("sla_hold_status", ["clear", "hold", "override"]);
|
|
5773
|
+
|
|
5774
|
+
export const slaSnapshots = pgTable(
|
|
5775
|
+
"sla_snapshots",
|
|
5776
|
+
{
|
|
5777
|
+
id: serial("id").primaryKey(),
|
|
5778
|
+
mediaOrderId: text("media_order_id").notNull().references(() => mediaOrders.id),
|
|
5779
|
+
ticketId: text("ticket_id").notNull(),
|
|
5780
|
+
holdStatus: slaHoldStatusEnum("hold_status").notNull().default("clear"),
|
|
5781
|
+
holdReason: text("hold_reason"),
|
|
5782
|
+
slaDays: integer("sla_days").notNull(),
|
|
5783
|
+
availableDays: numeric("available_days"),
|
|
5784
|
+
slaDeadline: timestamp("sla_deadline"),
|
|
5785
|
+
campaignStartDate: date("campaign_start_date"),
|
|
5786
|
+
milestones: jsonb("milestones"),
|
|
5787
|
+
profileName: text("profile_name"),
|
|
5788
|
+
lastUpdatedAt: timestamp("last_updated_at").defaultNow(),
|
|
5789
|
+
},
|
|
5790
|
+
(table) => [
|
|
5791
|
+
index("sla_snapshots_media_order_id_idx").on(table.mediaOrderId),
|
|
5792
|
+
index("sla_snapshots_ticket_id_idx").on(table.ticketId),
|
|
5793
|
+
uniqueIndex("sla_snapshots_order_ticket_uniq").on(table.mediaOrderId, table.ticketId),
|
|
5794
|
+
]
|
|
5795
|
+
);
|
|
5796
|
+
|
|
5797
|
+
export type SlaSnapshot = typeof slaSnapshots.$inferSelect;
|
|
5798
|
+
export type InsertSlaSnapshot = typeof slaSnapshots.$inferInsert;
|
|
5799
|
+
|
|
5636
5800
|
// ─── Inventory Sync Audit Trail ─────────────────────────────────────────────
|
|
5637
5801
|
|
|
5638
5802
|
/**
|
|
@@ -8205,3 +8369,131 @@ export const creativeDeliveries = pgTable("creative_deliveries", {
|
|
|
8205
8369
|
assetCount: integer("asset_count").notNull().default(0),
|
|
8206
8370
|
receivedAt: timestamp("received_at").defaultNow().notNull(),
|
|
8207
8371
|
});
|
|
8372
|
+
|
|
8373
|
+
// ─── Order Activity Feed ────────────────────────────────────────────────────
|
|
8374
|
+
|
|
8375
|
+
export const orderActivityFeed = pgTable(
|
|
8376
|
+
"order_activity_feed",
|
|
8377
|
+
{
|
|
8378
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
8379
|
+
mediaOrderId: varchar("media_order_id")
|
|
8380
|
+
.notNull()
|
|
8381
|
+
.references(() => mediaOrders.id),
|
|
8382
|
+
lineItemId: varchar("line_item_id").references(() => mediaOrderLineItems.id),
|
|
8383
|
+
eventType: activityEventTypeEnum("event_type").notNull(),
|
|
8384
|
+
title: text("title").notNull(),
|
|
8385
|
+
detail: text("detail"),
|
|
8386
|
+
severity: activitySeverityEnum("severity").notNull(),
|
|
8387
|
+
sourceSystem: activitySourceSystemEnum("source_system").notNull(),
|
|
8388
|
+
externalId: text("external_id").notNull().unique(), // idempotency key — prevents duplicate event ingestion
|
|
8389
|
+
status: text("status"), // intentionally untyped — values vary by eventType (creative vs fulfillment vs contract)
|
|
8390
|
+
occurredAt: timestamp("occurred_at").notNull(),
|
|
8391
|
+
createdAt: timestamp("created_at").notNull().defaultNow(),
|
|
8392
|
+
metadata: jsonb("metadata").$type<Record<string, unknown>>(),
|
|
8393
|
+
},
|
|
8394
|
+
(table) => [
|
|
8395
|
+
index("order_activity_feed_order_occurred_idx").on(
|
|
8396
|
+
table.mediaOrderId,
|
|
8397
|
+
table.occurredAt,
|
|
8398
|
+
),
|
|
8399
|
+
index("order_activity_feed_line_item_idx").on(table.lineItemId),
|
|
8400
|
+
],
|
|
8401
|
+
);
|
|
8402
|
+
|
|
8403
|
+
export type OrderActivityFeedEntry = typeof orderActivityFeed.$inferSelect;
|
|
8404
|
+
|
|
8405
|
+
// ─── Pending Actions ────────────────────────────────────────────────────────
|
|
8406
|
+
|
|
8407
|
+
export const pendingActions = pgTable(
|
|
8408
|
+
"pending_actions",
|
|
8409
|
+
{
|
|
8410
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
8411
|
+
mediaOrderId: varchar("media_order_id")
|
|
8412
|
+
.notNull()
|
|
8413
|
+
.references(() => mediaOrders.id),
|
|
8414
|
+
lineItemId: varchar("line_item_id").references(() => mediaOrderLineItems.id),
|
|
8415
|
+
actionType: pendingActionTypeEnum("action_type").notNull(),
|
|
8416
|
+
message: text("message").notNull(),
|
|
8417
|
+
activityFeedId: uuid("activity_feed_id")
|
|
8418
|
+
.notNull()
|
|
8419
|
+
.references(() => orderActivityFeed.id),
|
|
8420
|
+
resolvedAt: timestamp("resolved_at"),
|
|
8421
|
+
createdAt: timestamp("created_at").notNull().defaultNow(),
|
|
8422
|
+
},
|
|
8423
|
+
(table) => [
|
|
8424
|
+
index("pending_actions_order_idx").on(table.mediaOrderId),
|
|
8425
|
+
index("pending_actions_unresolved_idx").on(table.mediaOrderId).where(
|
|
8426
|
+
sql`resolved_at IS NULL`,
|
|
8427
|
+
),
|
|
8428
|
+
],
|
|
8429
|
+
);
|
|
8430
|
+
|
|
8431
|
+
export type PendingAction = typeof pendingActions.$inferSelect;
|
|
8432
|
+
|
|
8433
|
+
// ─── Order Version Snapshots ────────────────────────────────────────────────
|
|
8434
|
+
|
|
8435
|
+
export const orderVersionSnapshots = pgTable(
|
|
8436
|
+
"order_version_snapshots",
|
|
8437
|
+
{
|
|
8438
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
8439
|
+
mediaOrderId: varchar("media_order_id")
|
|
8440
|
+
.notNull()
|
|
8441
|
+
.references(() => mediaOrders.id),
|
|
8442
|
+
version: integer("version").notNull(),
|
|
8443
|
+
amendmentReason: text("amendment_reason"),
|
|
8444
|
+
amendmentNotes: text("amendment_notes"),
|
|
8445
|
+
amendedBy: varchar("amended_by"),
|
|
8446
|
+
snapshot: jsonb("snapshot").notNull().$type<Record<string, unknown>>(),
|
|
8447
|
+
netInvestment: numeric("net_investment", { precision: 12, scale: 2 }),
|
|
8448
|
+
lineItemCount: integer("line_item_count"),
|
|
8449
|
+
createdAt: timestamp("created_at").notNull().defaultNow(),
|
|
8450
|
+
},
|
|
8451
|
+
(table) => [
|
|
8452
|
+
index("order_version_snapshots_order_version_idx").on(
|
|
8453
|
+
table.mediaOrderId,
|
|
8454
|
+
table.version,
|
|
8455
|
+
),
|
|
8456
|
+
],
|
|
8457
|
+
);
|
|
8458
|
+
|
|
8459
|
+
export type OrderVersionSnapshot = typeof orderVersionSnapshots.$inferSelect;
|
|
8460
|
+
|
|
8461
|
+
// ─── User Milestones ────────────────────────────────────────────────────────
|
|
8462
|
+
|
|
8463
|
+
export const userMilestones = pgTable(
|
|
8464
|
+
"user_milestones",
|
|
8465
|
+
{
|
|
8466
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
8467
|
+
userId: varchar("user_id").notNull(),
|
|
8468
|
+
milestoneKey: text("milestone_key").notNull(),
|
|
8469
|
+
shownAt: timestamp("shown_at").notNull().defaultNow(),
|
|
8470
|
+
createdAt: timestamp("created_at").notNull().defaultNow(),
|
|
8471
|
+
},
|
|
8472
|
+
(table) => [
|
|
8473
|
+
index("user_milestones_user_id_idx").on(table.userId),
|
|
8474
|
+
unique("user_milestones_user_key_unique").on(table.userId, table.milestoneKey),
|
|
8475
|
+
],
|
|
8476
|
+
);
|
|
8477
|
+
|
|
8478
|
+
export type UserMilestone = typeof userMilestones.$inferSelect;
|
|
8479
|
+
|
|
8480
|
+
/** Shape of the ad_activity JSONB column in business_intel_cache */
|
|
8481
|
+
export interface BusinessIntelAdActivity {
|
|
8482
|
+
google?: { active: boolean; adCount: number; lastSeen?: string };
|
|
8483
|
+
meta?: { active: boolean; adCount: number; lastSeen?: string };
|
|
8484
|
+
}
|
|
8485
|
+
|
|
8486
|
+
/** Shape of the seo_snapshot JSONB column in business_intel_cache */
|
|
8487
|
+
export interface BusinessIntelSeoSnapshot {
|
|
8488
|
+
organicTraffic?: number;
|
|
8489
|
+
topKeywords?: string[];
|
|
8490
|
+
paidTrafficCost?: number;
|
|
8491
|
+
}
|
|
8492
|
+
|
|
8493
|
+
/** Shape of the tech_signals JSONB column in business_intel_cache */
|
|
8494
|
+
export interface BusinessIntelTechSignals {
|
|
8495
|
+
analytics?: string[];
|
|
8496
|
+
advertising?: string[];
|
|
8497
|
+
crm?: string[];
|
|
8498
|
+
social?: string[];
|
|
8499
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types for the order activity feed — the backbone for all
|
|
3
|
+
* post-submission visibility (contract alerts, fulfillment status,
|
|
4
|
+
* creative status, amendments, rate exception resolution).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export const ACTIVITY_EVENT_TYPES = [
|
|
8
|
+
"fulfillment_stage_changed",
|
|
9
|
+
"creative_status_changed",
|
|
10
|
+
"creative_delivered",
|
|
11
|
+
"signing_progress",
|
|
12
|
+
"drawdown_alert",
|
|
13
|
+
"renewal_reminder",
|
|
14
|
+
"amendment_submitted",
|
|
15
|
+
"rate_exception_resolved",
|
|
16
|
+
"sla_hold_set",
|
|
17
|
+
"sla_hold_cleared",
|
|
18
|
+
"sla_milestone_at_risk",
|
|
19
|
+
"sla_milestone_breached",
|
|
20
|
+
"sla_evaluated",
|
|
21
|
+
] as const;
|
|
22
|
+
|
|
23
|
+
export type ActivityEventType = (typeof ACTIVITY_EVENT_TYPES)[number];
|
|
24
|
+
|
|
25
|
+
export const ACTIVITY_SEVERITIES = [
|
|
26
|
+
"info",
|
|
27
|
+
"success",
|
|
28
|
+
"warning",
|
|
29
|
+
"action_needed",
|
|
30
|
+
] as const;
|
|
31
|
+
|
|
32
|
+
export type ActivitySeverity = (typeof ACTIVITY_SEVERITIES)[number];
|
|
33
|
+
|
|
34
|
+
export const ACTIVITY_SOURCE_SYSTEMS = [
|
|
35
|
+
"flux",
|
|
36
|
+
"forge",
|
|
37
|
+
"hubspot",
|
|
38
|
+
"compass",
|
|
39
|
+
] as const;
|
|
40
|
+
|
|
41
|
+
export type ActivitySourceSystem = (typeof ACTIVITY_SOURCE_SYSTEMS)[number];
|
|
42
|
+
|
|
43
|
+
export const PENDING_ACTION_TYPES = [
|
|
44
|
+
"creative_revision_needed",
|
|
45
|
+
"creative_rejected",
|
|
46
|
+
"fulfillment_blocked",
|
|
47
|
+
"rate_exception_pending",
|
|
48
|
+
"sla_hold_active",
|
|
49
|
+
] as const;
|
|
50
|
+
|
|
51
|
+
export type PendingActionType = (typeof PENDING_ACTION_TYPES)[number];
|
|
52
|
+
|
|
53
|
+
/** The event envelope fn-flux sends to fn-legacy */
|
|
54
|
+
export interface CompassStatusEvent {
|
|
55
|
+
schemaVersion: "v1";
|
|
56
|
+
eventId: string;
|
|
57
|
+
eventType: string;
|
|
58
|
+
idempotencyKey: string;
|
|
59
|
+
emittedAt: string;
|
|
60
|
+
payload: {
|
|
61
|
+
mediaOrderId: string;
|
|
62
|
+
lineItemId?: string | null;
|
|
63
|
+
hubspotCompanyId?: string | null;
|
|
64
|
+
title: string;
|
|
65
|
+
detail?: string | null;
|
|
66
|
+
severity: ActivitySeverity;
|
|
67
|
+
status?: string | null;
|
|
68
|
+
metadata?: Record<string, unknown> | null;
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Resolution mapping: which events clear which pending actions */
|
|
73
|
+
export const PENDING_ACTION_RESOLUTION_MAP: Record<
|
|
74
|
+
PendingActionType,
|
|
75
|
+
{ resolvedByEventType: ActivityEventType; resolvedByStatuses: string[] }
|
|
76
|
+
> = {
|
|
77
|
+
creative_revision_needed: {
|
|
78
|
+
resolvedByEventType: "creative_status_changed",
|
|
79
|
+
resolvedByStatuses: ["approved", "completed"],
|
|
80
|
+
},
|
|
81
|
+
creative_rejected: {
|
|
82
|
+
resolvedByEventType: "creative_status_changed",
|
|
83
|
+
resolvedByStatuses: ["approved"],
|
|
84
|
+
},
|
|
85
|
+
fulfillment_blocked: {
|
|
86
|
+
resolvedByEventType: "fulfillment_stage_changed",
|
|
87
|
+
resolvedByStatuses: ["pending_fulfillment", "running", "completed"],
|
|
88
|
+
},
|
|
89
|
+
rate_exception_pending: {
|
|
90
|
+
resolvedByEventType: "rate_exception_resolved",
|
|
91
|
+
resolvedByStatuses: ["approved", "auto_approved", "rejected"],
|
|
92
|
+
},
|
|
93
|
+
sla_hold_active: {
|
|
94
|
+
resolvedByEventType: "sla_hold_cleared",
|
|
95
|
+
resolvedByStatuses: ["clear"],
|
|
96
|
+
},
|
|
97
|
+
};
|