Add follow-up status tracking slice
This commit is contained in:
89
lib/outreach-follow-up.ts
Normal file
89
lib/outreach-follow-up.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import type {
|
||||
OutreachResponseStatus,
|
||||
OutreachSalesStatus,
|
||||
OutreachSendStatus,
|
||||
} from "./dashboard-model";
|
||||
|
||||
export const FOLLOW_UP_DUE_DELAY_MS = 7 * 24 * 60 * 60 * 1000;
|
||||
export const DO_NOT_CONTACT_RECHECK_MS = 365 * 24 * 60 * 60 * 1000;
|
||||
|
||||
export type FollowUpPromptState = "not_ready" | "pending" | "due" | "suppressed";
|
||||
|
||||
export const manualSalesStatusLabels: Record<OutreachSalesStatus, string> = {
|
||||
follow_up_planned: "Follow-up geplant",
|
||||
follow_up_sent: "Follow-up gesendet",
|
||||
reply_received: "Antwort erhalten",
|
||||
not_interested: "Kein Interesse",
|
||||
later: "Später wieder melden",
|
||||
meeting_scheduled: "Gespräch vereinbart",
|
||||
proposal_requested: "Angebot angefragt",
|
||||
proposal_sent: "Angebot gesendet",
|
||||
won: "Auftrag gewonnen",
|
||||
lost: "Auftrag verloren",
|
||||
do_not_pursue: "Nicht weiter verfolgen",
|
||||
};
|
||||
|
||||
const suppressingSalesStatuses = new Set<OutreachSalesStatus>([
|
||||
"reply_received",
|
||||
"not_interested",
|
||||
"do_not_pursue",
|
||||
"follow_up_sent",
|
||||
]);
|
||||
|
||||
const suppressingResponseStatuses = new Set<OutreachResponseStatus>([
|
||||
"manual_reply_recorded",
|
||||
"no_interest",
|
||||
]);
|
||||
|
||||
export function getManualSalesStatusLabel(status: OutreachSalesStatus) {
|
||||
return manualSalesStatusLabels[status];
|
||||
}
|
||||
|
||||
export function shouldCreateFollowUpDraftAfterSend(input: {
|
||||
existingFollowUpOutreachCount: number;
|
||||
followUpDraft?: string | null;
|
||||
salesStatus: OutreachSalesStatus;
|
||||
sendStatus: OutreachSendStatus;
|
||||
}) {
|
||||
return (
|
||||
input.sendStatus === "sent" &&
|
||||
input.salesStatus === "follow_up_planned" &&
|
||||
input.existingFollowUpOutreachCount === 0 &&
|
||||
Boolean(input.followUpDraft?.trim())
|
||||
);
|
||||
}
|
||||
|
||||
export function getFollowUpPromptState(input: {
|
||||
followUpDueAt?: number | null;
|
||||
responseStatus: OutreachResponseStatus;
|
||||
salesStatus: OutreachSalesStatus;
|
||||
now: number;
|
||||
}): FollowUpPromptState {
|
||||
if (
|
||||
suppressingSalesStatuses.has(input.salesStatus) ||
|
||||
suppressingResponseStatuses.has(input.responseStatus)
|
||||
) {
|
||||
return "suppressed";
|
||||
}
|
||||
|
||||
if (typeof input.followUpDueAt !== "number") {
|
||||
return "not_ready";
|
||||
}
|
||||
|
||||
return input.now >= input.followUpDueAt ? "due" : "pending";
|
||||
}
|
||||
|
||||
export function getDoNotContactRecheckState(input: {
|
||||
doNotContactUntil?: number | null;
|
||||
now: number;
|
||||
}) {
|
||||
if (typeof input.doNotContactUntil !== "number") {
|
||||
return { status: "none" as const, label: "Offen" };
|
||||
}
|
||||
|
||||
if (input.now >= input.doNotContactUntil) {
|
||||
return { status: "recheck" as const, label: "Erneut prüfen" };
|
||||
}
|
||||
|
||||
return { status: "blocked" as const, label: "Nicht erneut kontaktieren" };
|
||||
}
|
||||
Reference in New Issue
Block a user