@foundrynorth/compass-schema 1.0.22 → 1.0.24
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.d.ts +1197 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +174 -1
- package/dist/schema.js.map +1 -1
- package/dist/types/activity-feed.d.ts +2 -2
- package/dist/types/activity-feed.d.ts.map +1 -1
- package/dist/types/activity-feed.js +8 -0
- package/dist/types/activity-feed.js.map +1 -1
- package/package.json +1 -1
- package/src/schema.ts +239 -1
- package/src/types/activity-feed.ts +8 -0
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
* post-submission visibility (contract alerts, fulfillment status,
|
|
4
4
|
* creative status, amendments, rate exception resolution).
|
|
5
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"];
|
|
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", "mid_campaign_change_applied", "mid_campaign_task_state_changed", "mid_campaign_change_verified"];
|
|
7
7
|
export type ActivityEventType = (typeof ACTIVITY_EVENT_TYPES)[number];
|
|
8
8
|
export declare const ACTIVITY_SEVERITIES: readonly ["info", "success", "warning", "action_needed"];
|
|
9
9
|
export type ActivitySeverity = (typeof ACTIVITY_SEVERITIES)[number];
|
|
10
10
|
export declare const ACTIVITY_SOURCE_SYSTEMS: readonly ["flux", "forge", "hubspot", "compass"];
|
|
11
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"];
|
|
12
|
+
export declare const PENDING_ACTION_TYPES: readonly ["creative_revision_needed", "creative_rejected", "fulfillment_blocked", "rate_exception_pending", "sla_hold_active", "mid_campaign_tasks_pending"];
|
|
13
13
|
export type PendingActionType = (typeof PENDING_ACTION_TYPES)[number];
|
|
14
14
|
/** The event envelope fn-flux sends to fn-legacy */
|
|
15
15
|
export interface CompassStatusEvent {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"activity-feed.d.ts","sourceRoot":"","sources":["../../src/types/activity-feed.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,eAAO,MAAM,oBAAoB,
|
|
1
|
+
{"version":3,"file":"activity-feed.d.ts","sourceRoot":"","sources":["../../src/types/activity-feed.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,eAAO,MAAM,oBAAoB,+YAiBvB,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,8JAOvB,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,CA0BzE,CAAC"}
|
|
@@ -17,6 +17,9 @@ export const ACTIVITY_EVENT_TYPES = [
|
|
|
17
17
|
"sla_milestone_at_risk",
|
|
18
18
|
"sla_milestone_breached",
|
|
19
19
|
"sla_evaluated",
|
|
20
|
+
"mid_campaign_change_applied",
|
|
21
|
+
"mid_campaign_task_state_changed",
|
|
22
|
+
"mid_campaign_change_verified",
|
|
20
23
|
];
|
|
21
24
|
export const ACTIVITY_SEVERITIES = [
|
|
22
25
|
"info",
|
|
@@ -36,6 +39,7 @@ export const PENDING_ACTION_TYPES = [
|
|
|
36
39
|
"fulfillment_blocked",
|
|
37
40
|
"rate_exception_pending",
|
|
38
41
|
"sla_hold_active",
|
|
42
|
+
"mid_campaign_tasks_pending",
|
|
39
43
|
];
|
|
40
44
|
/** Resolution mapping: which events clear which pending actions */
|
|
41
45
|
export const PENDING_ACTION_RESOLUTION_MAP = {
|
|
@@ -59,5 +63,9 @@ export const PENDING_ACTION_RESOLUTION_MAP = {
|
|
|
59
63
|
resolvedByEventType: "sla_hold_cleared",
|
|
60
64
|
resolvedByStatuses: ["clear"],
|
|
61
65
|
},
|
|
66
|
+
mid_campaign_tasks_pending: {
|
|
67
|
+
resolvedByEventType: "mid_campaign_change_verified",
|
|
68
|
+
resolvedByStatuses: ["verified"],
|
|
69
|
+
},
|
|
62
70
|
};
|
|
63
71
|
//# sourceMappingURL=activity-feed.js.map
|
|
@@ -1 +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;
|
|
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;IACf,6BAA6B;IAC7B,iCAAiC;IACjC,8BAA8B;CACtB,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;IACjB,4BAA4B;CACpB,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;IACD,0BAA0B,EAAE;QAC1B,mBAAmB,EAAE,8BAA8B;QACnD,kBAAkB,EAAE,CAAC,UAAU,CAAC;KACjC;CACF,CAAC"}
|
package/package.json
CHANGED
package/src/schema.ts
CHANGED
|
@@ -8503,7 +8503,15 @@ export type SemCampaignRecord = typeof semCampaigns.$inferSelect;
|
|
|
8503
8503
|
export type InsertSemCampaign = typeof semCampaigns.$inferInsert;
|
|
8504
8504
|
|
|
8505
8505
|
// =============================================================================
|
|
8506
|
-
// COMPASS EVENT OUTBOX (
|
|
8506
|
+
// COMPASS EVENT OUTBOX (durable outbox for cross-system writes)
|
|
8507
|
+
//
|
|
8508
|
+
// Generic claim/retry/DLQ pattern used by multiple producers. Events are
|
|
8509
|
+
// namespaced by `eventType` prefix so multiple workers can drain the same
|
|
8510
|
+
// table without contention:
|
|
8511
|
+
// • "flux.*" — fn-flux webhook delivery to external consumers
|
|
8512
|
+
// • "hubspot.*" — HubSpot ticket / Campaign Task writes from fn-legacy
|
|
8513
|
+
// (drained by fn-v2 hubspot-outbox-worker)
|
|
8514
|
+
// Workers filter on `event_type LIKE '{namespace}.%'` in their claim query.
|
|
8507
8515
|
// =============================================================================
|
|
8508
8516
|
|
|
8509
8517
|
export const compassEventOutboxStatusEnum = pgEnum("compass_event_outbox_status", [
|
|
@@ -8667,6 +8675,236 @@ export const userMilestones = pgTable(
|
|
|
8667
8675
|
|
|
8668
8676
|
export type UserMilestone = typeof userMilestones.$inferSelect;
|
|
8669
8677
|
|
|
8678
|
+
// ─── Mid-Campaign Change Management ────────────────────────────────────────
|
|
8679
|
+
//
|
|
8680
|
+
// Finance-grade in-place edits to live media orders. Distinct from
|
|
8681
|
+
// AmendOrderWizard (which takes orders active→draft→sent). A mid-campaign
|
|
8682
|
+
// change keeps the order `active`, touches a surgical set of line items,
|
|
8683
|
+
// and routes through the existing rate-exception-engine if it would alter
|
|
8684
|
+
// rate rules or margin. HubSpot push is the canonical output — HubSpot's
|
|
8685
|
+
// own workflow pushes deltas to Naviga.
|
|
8686
|
+
//
|
|
8687
|
+
// State machine:
|
|
8688
|
+
// draft → (rate violation?) ─┬─ pending_rate_approval ─┬─ applied → hubspot_syncing → hubspot_synced
|
|
8689
|
+
// │ └─ rejected (terminal, line items rolled back)
|
|
8690
|
+
// └─ applied → hubspot_syncing ─┬─ hubspot_synced
|
|
8691
|
+
// └─ hubspot_failed (retryable)
|
|
8692
|
+
|
|
8693
|
+
export const midCampaignChangeRequests = pgTable(
|
|
8694
|
+
"mid_campaign_change_requests",
|
|
8695
|
+
{
|
|
8696
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
8697
|
+
mediaOrderId: varchar("media_order_id")
|
|
8698
|
+
.notNull()
|
|
8699
|
+
.references(() => mediaOrders.id),
|
|
8700
|
+
reason: amendmentReasonEnum("reason").notNull(),
|
|
8701
|
+
notes: text("notes").notNull(),
|
|
8702
|
+
requestedByUserId: varchar("requested_by_user_id").notNull(),
|
|
8703
|
+
requestedByEmail: varchar("requested_by_email"),
|
|
8704
|
+
requestedByName: varchar("requested_by_name"),
|
|
8705
|
+
requestedAt: timestamp("requested_at").notNull().defaultNow(),
|
|
8706
|
+
appliedAt: timestamp("applied_at"),
|
|
8707
|
+
status: varchar("status").notNull().default("draft"),
|
|
8708
|
+
// draft | pending_rate_approval | applied | rejected | hubspot_syncing | hubspot_synced | hubspot_failed | verified
|
|
8709
|
+
// `verified` is terminal — set when taskStateJson.allComplete && hubspotTicketStage is terminal.
|
|
8710
|
+
|
|
8711
|
+
// Denormalized summary (so finance reports never join into ops)
|
|
8712
|
+
totalInvestmentDelta: numeric("total_investment_delta", { precision: 12, scale: 2 }).notNull().default("0"),
|
|
8713
|
+
affectedLineItemCount: integer("affected_line_item_count").notNull().default(0),
|
|
8714
|
+
marginDeltaPercent: numeric("margin_delta_percent", { precision: 7, scale: 4 }),
|
|
8715
|
+
marginDeltaDollars: numeric("margin_delta_dollars", { precision: 12, scale: 2 }),
|
|
8716
|
+
|
|
8717
|
+
// Approval state (nullable — only set when rate engine returned pending_approval for any op)
|
|
8718
|
+
rateApprovalCompletedAt: timestamp("rate_approval_completed_at"),
|
|
8719
|
+
rejectionReason: text("rejection_reason"),
|
|
8720
|
+
|
|
8721
|
+
// HubSpot ticket (fulfillment pipeline, CHANGE_REQUIRED stage)
|
|
8722
|
+
hubspotTicketId: text("hubspot_ticket_id"),
|
|
8723
|
+
hubspotCampaignTaskIds: jsonb("hubspot_campaign_task_ids").$type<string[]>().default(sql`'[]'::jsonb`),
|
|
8724
|
+
|
|
8725
|
+
// HubSpot line-item sync (via fn-v2 hubspot-order-sync)
|
|
8726
|
+
hubspotSyncRunId: text("hubspot_sync_run_id"),
|
|
8727
|
+
hubspotSyncStartedAt: timestamp("hubspot_sync_started_at"),
|
|
8728
|
+
hubspotSyncCompletedAt: timestamp("hubspot_sync_completed_at"),
|
|
8729
|
+
hubspotSyncError: text("hubspot_sync_error"),
|
|
8730
|
+
|
|
8731
|
+
// Closed-loop projection from HubSpot Campaign Task webhooks
|
|
8732
|
+
// (updated by handleCampaignTaskChange in hubspot-mid-campaign-writeback.ts)
|
|
8733
|
+
hubspotTicketStage: varchar("hubspot_ticket_stage", { length: 40 }),
|
|
8734
|
+
// Current ticket stage as seen in the most recent webhook delivery.
|
|
8735
|
+
// Values: CHANGE_REQUIRED | BILLING_CHANGE_REQUIRED | CANCELLED | COMPLETED | etc.
|
|
8736
|
+
taskStateJson: jsonb("task_state_json").$type<{
|
|
8737
|
+
tasks: Array<{
|
|
8738
|
+
hubspotTaskId: string;
|
|
8739
|
+
templateId: string;
|
|
8740
|
+
queue: string;
|
|
8741
|
+
status: "not_started" | "in_progress" | "completed" | "waiting" | "blocked";
|
|
8742
|
+
completedAt: string | null;
|
|
8743
|
+
ownerId: string | null;
|
|
8744
|
+
ownerName: string | null;
|
|
8745
|
+
}>;
|
|
8746
|
+
completedCount: number;
|
|
8747
|
+
totalCount: number;
|
|
8748
|
+
allComplete: boolean;
|
|
8749
|
+
}>(),
|
|
8750
|
+
taskStateUpdatedAt: timestamp("task_state_updated_at"),
|
|
8751
|
+
verifiedAt: timestamp("verified_at"),
|
|
8752
|
+
|
|
8753
|
+
// Slack alert (thread follow-up replies on approval/sync completion)
|
|
8754
|
+
slackMessageTs: text("slack_message_ts"),
|
|
8755
|
+
slackChannelId: text("slack_channel_id"),
|
|
8756
|
+
|
|
8757
|
+
// Snapshot for rollback on rate-exception rejection
|
|
8758
|
+
rollbackSnapshotVersion: integer("rollback_snapshot_version"),
|
|
8759
|
+
|
|
8760
|
+
createdAt: timestamp("created_at").notNull().defaultNow(),
|
|
8761
|
+
updatedAt: timestamp("updated_at").notNull().defaultNow(),
|
|
8762
|
+
},
|
|
8763
|
+
(table) => [
|
|
8764
|
+
index("mcc_requests_order_idx").on(table.mediaOrderId),
|
|
8765
|
+
index("mcc_requests_status_idx").on(table.status),
|
|
8766
|
+
index("mcc_requests_requested_at_idx").on(table.requestedAt),
|
|
8767
|
+
index("mcc_requests_order_status_idx").on(table.mediaOrderId, table.status),
|
|
8768
|
+
],
|
|
8769
|
+
);
|
|
8770
|
+
|
|
8771
|
+
export type MidCampaignChangeRequest = typeof midCampaignChangeRequests.$inferSelect;
|
|
8772
|
+
export type InsertMidCampaignChangeRequest = typeof midCampaignChangeRequests.$inferInsert;
|
|
8773
|
+
|
|
8774
|
+
/**
|
|
8775
|
+
* One row per line-item operation inside a change request. This is the
|
|
8776
|
+
* finance report source — one row per field changed per line item.
|
|
8777
|
+
*/
|
|
8778
|
+
export const midCampaignChangeOps = pgTable(
|
|
8779
|
+
"mid_campaign_change_ops",
|
|
8780
|
+
{
|
|
8781
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
8782
|
+
changeRequestId: uuid("change_request_id")
|
|
8783
|
+
.notNull()
|
|
8784
|
+
.references(() => midCampaignChangeRequests.id, { onDelete: "cascade" }),
|
|
8785
|
+
opType: varchar("op_type").notNull(), // update | cancel | create
|
|
8786
|
+
lineItemId: varchar("line_item_id")
|
|
8787
|
+
.notNull()
|
|
8788
|
+
.references(() => mediaOrderLineItems.id),
|
|
8789
|
+
// For cancel+create pairs (date-shift across month boundary, product swap)
|
|
8790
|
+
supersedesLineItemId: varchar("supersedes_line_item_id").references(() => mediaOrderLineItems.id),
|
|
8791
|
+
|
|
8792
|
+
// Denormalized for reporting (so the report query is one table scan)
|
|
8793
|
+
periodLabel: text("period_label"), // "Aug 2026"
|
|
8794
|
+
productCode: text("product_code"),
|
|
8795
|
+
productName: text("product_name"),
|
|
8796
|
+
placement: text("placement"),
|
|
8797
|
+
|
|
8798
|
+
// The per-field diff — this is the atom finance sees
|
|
8799
|
+
fieldChanges: jsonb("field_changes")
|
|
8800
|
+
.notNull()
|
|
8801
|
+
.$type<Array<{ field: string; from: string | number | null; to: string | number | null }>>(),
|
|
8802
|
+
|
|
8803
|
+
// Per-op investment delta (sums to totalInvestmentDelta on the request)
|
|
8804
|
+
investmentDelta: numeric("investment_delta", { precision: 12, scale: 2 }).notNull().default("0"),
|
|
8805
|
+
|
|
8806
|
+
// Tracks which HubSpot action this op triggered when synced
|
|
8807
|
+
hubspotOp: varchar("hubspot_op"), // update | cancel | create | skipped
|
|
8808
|
+
hubspotLineItemIdBefore: text("hubspot_line_item_id_before"),
|
|
8809
|
+
hubspotLineItemIdAfter: text("hubspot_line_item_id_after"),
|
|
8810
|
+
|
|
8811
|
+
createdAt: timestamp("created_at").notNull().defaultNow(),
|
|
8812
|
+
},
|
|
8813
|
+
(table) => [
|
|
8814
|
+
index("mcc_ops_change_request_idx").on(table.changeRequestId),
|
|
8815
|
+
index("mcc_ops_line_item_idx").on(table.lineItemId),
|
|
8816
|
+
index("mcc_ops_period_idx").on(table.periodLabel),
|
|
8817
|
+
uniqueIndex("mcc_ops_change_request_line_item_unique").on(
|
|
8818
|
+
table.changeRequestId,
|
|
8819
|
+
table.lineItemId,
|
|
8820
|
+
),
|
|
8821
|
+
],
|
|
8822
|
+
);
|
|
8823
|
+
|
|
8824
|
+
export type MidCampaignChangeOp = typeof midCampaignChangeOps.$inferSelect;
|
|
8825
|
+
export type InsertMidCampaignChangeOp = typeof midCampaignChangeOps.$inferInsert;
|
|
8826
|
+
|
|
8827
|
+
/**
|
|
8828
|
+
* Join table: change requests ↔ rate exception requests. A single change
|
|
8829
|
+
* request may trigger N rate exceptions (one per line item that violates
|
|
8830
|
+
* rate rules). The change request blocks in `pending_rate_approval` until
|
|
8831
|
+
* every linked exception is decided; if any reject, the change rolls back.
|
|
8832
|
+
*/
|
|
8833
|
+
export const midCampaignChangeRateExceptions = pgTable(
|
|
8834
|
+
"mid_campaign_change_rate_exceptions",
|
|
8835
|
+
{
|
|
8836
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
8837
|
+
changeRequestId: uuid("change_request_id")
|
|
8838
|
+
.notNull()
|
|
8839
|
+
.references(() => midCampaignChangeRequests.id, { onDelete: "cascade" }),
|
|
8840
|
+
rateExceptionRequestId: uuid("rate_exception_request_id")
|
|
8841
|
+
.notNull()
|
|
8842
|
+
.references(() => rateExceptionRequests.id, { onDelete: "restrict" }),
|
|
8843
|
+
// Which op on the change request this exception corresponds to
|
|
8844
|
+
changeOpId: uuid("change_op_id").references(() => midCampaignChangeOps.id, { onDelete: "cascade" }),
|
|
8845
|
+
createdAt: timestamp("created_at").notNull().defaultNow(),
|
|
8846
|
+
},
|
|
8847
|
+
(table) => [
|
|
8848
|
+
index("mcc_rate_exc_change_request_idx").on(table.changeRequestId),
|
|
8849
|
+
index("mcc_rate_exc_exception_idx").on(table.rateExceptionRequestId),
|
|
8850
|
+
uniqueIndex("mcc_rate_exc_change_request_exception_unique").on(
|
|
8851
|
+
table.changeRequestId,
|
|
8852
|
+
table.rateExceptionRequestId,
|
|
8853
|
+
),
|
|
8854
|
+
],
|
|
8855
|
+
);
|
|
8856
|
+
|
|
8857
|
+
export type MidCampaignChangeRateException = typeof midCampaignChangeRateExceptions.$inferSelect;
|
|
8858
|
+
export type InsertMidCampaignChangeRateException = typeof midCampaignChangeRateExceptions.$inferInsert;
|
|
8859
|
+
|
|
8860
|
+
/**
|
|
8861
|
+
* Campaign Task templates for mid-campaign changes. Hybrid storage — config
|
|
8862
|
+
* in DB (queue, offset, priority, is_active, display_order) so admins can
|
|
8863
|
+
* hot-edit without deploys; predicate logic stays in a code registry
|
|
8864
|
+
* (mid-campaign-template-predicates.ts) keyed by `predicateKey`.
|
|
8865
|
+
*
|
|
8866
|
+
* CI validator asserts every row's `queue` value is in the live HubSpot
|
|
8867
|
+
* Campaign Task `queue` enum. Prevents the "wrong queue ships to HubSpot and
|
|
8868
|
+
* silently fails enum validation" bug.
|
|
8869
|
+
*/
|
|
8870
|
+
export const midCampaignTaskTemplates = pgTable(
|
|
8871
|
+
"mid_campaign_task_templates",
|
|
8872
|
+
{
|
|
8873
|
+
// Stable key (e.g. "review_change_request", "verify_billing_impact").
|
|
8874
|
+
// Used as the templateId in the task plan and for deduplication.
|
|
8875
|
+
id: varchar("id", { length: 64 }).primaryKey(),
|
|
8876
|
+
// Human-readable name — sent as campaign_task_name to HubSpot.
|
|
8877
|
+
name: text("name").notNull(),
|
|
8878
|
+
// Portal Campaign Task queue enum value. CI validator enforces.
|
|
8879
|
+
queue: varchar("queue", { length: 40 }).notNull(),
|
|
8880
|
+
// Days from applied-at when the task is due. Negative = before apply.
|
|
8881
|
+
offsetDays: integer("offset_days").notNull().default(0),
|
|
8882
|
+
// Written to HubSpot priority: high | medium | low.
|
|
8883
|
+
priority: varchar("priority", { length: 16 }).notNull().default("medium"),
|
|
8884
|
+
// Key into the PREDICATES registry in code. Must resolve at runtime.
|
|
8885
|
+
// Valid: always, has_financial_change, has_digital_update_or_cancel,
|
|
8886
|
+
// has_digital_create, has_print_change, touches_dates
|
|
8887
|
+
predicateKey: varchar("predicate_key", { length: 64 }).notNull(),
|
|
8888
|
+
// Inactive templates are skipped by buildMidCampaignChangeTaskPlan.
|
|
8889
|
+
isActive: boolean("is_active").notNull().default(true),
|
|
8890
|
+
// Display/evaluation order. Lower = higher up in the Compass task list.
|
|
8891
|
+
displayOrder: integer("display_order").notNull().default(0),
|
|
8892
|
+
// Optional human description for the admin UI.
|
|
8893
|
+
description: text("description"),
|
|
8894
|
+
// Audit — who last edited and when.
|
|
8895
|
+
updatedAt: timestamp("updated_at").notNull().defaultNow(),
|
|
8896
|
+
updatedBy: varchar("updated_by"),
|
|
8897
|
+
createdAt: timestamp("created_at").notNull().defaultNow(),
|
|
8898
|
+
},
|
|
8899
|
+
(table) => [
|
|
8900
|
+
index("mid_campaign_task_templates_active_idx").on(table.isActive),
|
|
8901
|
+
index("mid_campaign_task_templates_order_idx").on(table.displayOrder),
|
|
8902
|
+
],
|
|
8903
|
+
);
|
|
8904
|
+
|
|
8905
|
+
export type MidCampaignTaskTemplate = typeof midCampaignTaskTemplates.$inferSelect;
|
|
8906
|
+
export type InsertMidCampaignTaskTemplate = typeof midCampaignTaskTemplates.$inferInsert;
|
|
8907
|
+
|
|
8670
8908
|
/** Shape of the ad_activity JSONB column in business_intel_cache */
|
|
8671
8909
|
export interface BusinessIntelAdActivity {
|
|
8672
8910
|
google?: { active: boolean; adCount: number; lastSeen?: string };
|
|
@@ -18,6 +18,9 @@ export const ACTIVITY_EVENT_TYPES = [
|
|
|
18
18
|
"sla_milestone_at_risk",
|
|
19
19
|
"sla_milestone_breached",
|
|
20
20
|
"sla_evaluated",
|
|
21
|
+
"mid_campaign_change_applied",
|
|
22
|
+
"mid_campaign_task_state_changed",
|
|
23
|
+
"mid_campaign_change_verified",
|
|
21
24
|
] as const;
|
|
22
25
|
|
|
23
26
|
export type ActivityEventType = (typeof ACTIVITY_EVENT_TYPES)[number];
|
|
@@ -46,6 +49,7 @@ export const PENDING_ACTION_TYPES = [
|
|
|
46
49
|
"fulfillment_blocked",
|
|
47
50
|
"rate_exception_pending",
|
|
48
51
|
"sla_hold_active",
|
|
52
|
+
"mid_campaign_tasks_pending",
|
|
49
53
|
] as const;
|
|
50
54
|
|
|
51
55
|
export type PendingActionType = (typeof PENDING_ACTION_TYPES)[number];
|
|
@@ -94,4 +98,8 @@ export const PENDING_ACTION_RESOLUTION_MAP: Record<
|
|
|
94
98
|
resolvedByEventType: "sla_hold_cleared",
|
|
95
99
|
resolvedByStatuses: ["clear"],
|
|
96
100
|
},
|
|
101
|
+
mid_campaign_tasks_pending: {
|
|
102
|
+
resolvedByEventType: "mid_campaign_change_verified",
|
|
103
|
+
resolvedByStatuses: ["verified"],
|
|
104
|
+
},
|
|
97
105
|
};
|