diff --git a/convex/crm.ts b/convex/crm.ts index b16a59fa..401f8183 100644 --- a/convex/crm.ts +++ b/convex/crm.ts @@ -147,6 +147,88 @@ function matchesSearch(values: Array, search: string) { return haystack.includes(search) } +function normalizeConversationStatus(value?: string) { + const normalized = String(value || "") + .trim() + .toLowerCase() + + if (!normalized) { + return "open" + } + + if ( + [ + "closed", + "completed", + "complete", + "ended", + "resolved", + "done", + ].includes(normalized) + ) { + return "closed" + } + + if ( + [ + "archived", + "spam", + "blocked", + "blacklisted", + "do_not_contact", + "dnd", + ].includes(normalized) + ) { + return "archived" + } + + return "open" +} + +function normalizeConversationDirection(value?: string) { + const normalized = String(value || "") + .trim() + .toLowerCase() + + if (normalized === "inbound") { + return "inbound" + } + + if (normalized === "outbound") { + return "outbound" + } + + return "mixed" +} + +function normalizeRecordingStatus(value?: string) { + const normalized = String(value || "") + .trim() + .toLowerCase() + + if (!normalized) { + return "pending" + } + + if (["completed", "complete", "ready", "available"].includes(normalized)) { + return "completed" + } + + if (["starting", "queued"].includes(normalized)) { + return "starting" + } + + if (["recording", "in_progress", "processing"].includes(normalized)) { + return "recording" + } + + if (["failed", "error"].includes(normalized)) { + return "failed" + } + + return "pending" +} + async function buildContactTimeline(ctx, contactId) { const conversations = await ctx.db .query("conversations") @@ -449,8 +531,8 @@ export const importConversation = mutation({ channel: payload.channel === "SMS" || payload.type === "sms" ? "sms" : "call", source: `${args.provider}:mirror`, - status: payload.status || "open", - direction: payload.direction || "mixed", + status: normalizeConversationStatus(payload.status), + direction: normalizeConversationDirection(payload.direction), startedAt: typeof payload.dateAdded === "string" ? new Date(payload.dateAdded).getTime() @@ -525,8 +607,8 @@ export const importMessage = mutation({ ? "call" : "unknown", source: `${args.provider}:mirror`, - status: "open", - direction: payload.direction || "mixed", + status: normalizeConversationStatus(payload.conversationStatus), + direction: normalizeConversationDirection(payload.direction), startedAt: typeof payload.dateAdded === "string" ? new Date(payload.dateAdded).getTime() @@ -605,7 +687,7 @@ export const importRecording = mutation({ channel: "call", source: `${args.provider}:mirror`, status: "closed", - direction: payload.direction || "mixed", + direction: normalizeConversationDirection(payload.direction), startedAt: typeof payload.createdAt === "string" ? new Date(payload.createdAt).getTime() @@ -624,7 +706,7 @@ export const importRecording = mutation({ source: `${args.provider}:mirror`, recordingId: payload.recordingId || payload.id || args.entityId, recordingUrl: payload.recordingUrl, - recordingStatus: payload.recordingStatus || "completed", + recordingStatus: normalizeRecordingStatus(payload.recordingStatus), transcriptionText: payload.transcript, durationMs: typeof payload.durationMs === "number" @@ -883,7 +965,7 @@ export const runGhlMirror = action({ entityId: "contacts", cursor: contactsCursor, status: "synced", - error: undefined, + error: "", metadata: JSON.stringify({ imported: summary.contacts, pages: contactsPages, @@ -958,7 +1040,7 @@ export const runGhlMirror = action({ entityId: "conversations", cursor: conversationCursors.Call || conversationCursors.SMS, status: "synced", - error: undefined, + error: "", metadata: JSON.stringify({ imported: summary.conversations, cursors: conversationCursors, @@ -1021,7 +1103,7 @@ export const runGhlMirror = action({ entityId: "messages", cursor: messageCursors.Call || messageCursors.SMS, status: "synced", - error: undefined, + error: "", metadata: JSON.stringify({ imported: summary.messages, cursors: messageCursors, @@ -1082,7 +1164,7 @@ export const runGhlMirror = action({ entityId: "recordings", cursor: String(nextPage), status: "synced", - error: undefined, + error: "", metadata: JSON.stringify({ imported: summary.recordings, nextPage, @@ -1107,7 +1189,9 @@ export const runGhlMirror = action({ entityType: "reconcile", entityId: "reconcile", status: reconcile.mismatches?.length ? "mismatch" : "reconciled", - error: undefined, + error: reconcile.mismatches?.length + ? "Some mirrored records are missing locally." + : "", metadata: JSON.stringify({ checked: reconcile.checked, mismatches: reconcile.mismatches || [],