275 lines
8.1 KiB
TypeScript
275 lines
8.1 KiB
TypeScript
import type { AdminTelephonyAction } from "@hivegrid/view-models";
|
|
import { writeAdminSalesAudit } from "@hivegrid/db/admin-sales-core.server";
|
|
|
|
import type { AdminTelephonyOperatorContext } from "./admin-telephony-connections.server";
|
|
import { adminPath } from "~/utils/app/CanonicalUrlUtils";
|
|
import { db } from "~/utils/db.server";
|
|
|
|
export function adminTelephonyWorkspaceHrefs() {
|
|
return {
|
|
calls: adminPath("sales/calls", { mode: "host-aware" }),
|
|
settings: adminPath("settings/telephony", {
|
|
mode: "host-aware",
|
|
}),
|
|
newLead: adminPath("leads/new", { mode: "host-aware" }),
|
|
};
|
|
}
|
|
|
|
export async function readAdminTelephonyWorkspace(
|
|
context: AdminTelephonyOperatorContext,
|
|
selectedCallId?: string | null
|
|
) {
|
|
const ownerWhere = context.canReadAllCommunication
|
|
? {}
|
|
: { adminUserId: context.adminUserId };
|
|
const [calls, dispositions, contacts, policy, dialerLists] =
|
|
await Promise.all([
|
|
db.adminCallSession.findMany({
|
|
where: ownerWhere,
|
|
orderBy: [{ startedAt: "desc" }, { id: "asc" }],
|
|
take: 100,
|
|
include: {
|
|
contact: {
|
|
select: { firstName: true, lastName: true },
|
|
},
|
|
lead: {
|
|
select: {
|
|
firstName: true,
|
|
lastName: true,
|
|
email: true,
|
|
},
|
|
},
|
|
deal: { select: { title: true } },
|
|
recording: {
|
|
select: { status: true, retainedUntil: true },
|
|
},
|
|
matchCandidates: {
|
|
orderBy: [
|
|
{ confidenceBasisPoints: "desc" },
|
|
{ createdAt: "asc" },
|
|
],
|
|
include: {
|
|
contact: {
|
|
select: {
|
|
firstName: true,
|
|
lastName: true,
|
|
deals: {
|
|
where: { deal: { status: "open" } },
|
|
orderBy: [
|
|
{ isPrimary: "desc" },
|
|
{ createdAt: "asc" },
|
|
],
|
|
take: 1,
|
|
select: { dealId: true },
|
|
},
|
|
},
|
|
},
|
|
lead: {
|
|
select: {
|
|
firstName: true,
|
|
lastName: true,
|
|
email: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}),
|
|
db.adminCallDisposition.findMany({
|
|
where: { active: true, archivedAt: null },
|
|
orderBy: [{ position: "asc" }, { code: "asc" }],
|
|
select: { code: true },
|
|
}),
|
|
db.adminContact.findMany({
|
|
where: {
|
|
archivedAt: null,
|
|
normalizedPhone: { not: null },
|
|
},
|
|
orderBy: [{ lastName: "asc" }, { firstName: "asc" }, { id: "asc" }],
|
|
take: 500,
|
|
select: {
|
|
id: true,
|
|
firstName: true,
|
|
lastName: true,
|
|
normalizedPhone: true,
|
|
doNotCall: true,
|
|
deals: {
|
|
where: { deal: { status: "open" } },
|
|
orderBy: [{ isPrimary: "desc" }, { createdAt: "asc" }],
|
|
take: 1,
|
|
select: { dealId: true },
|
|
},
|
|
},
|
|
}),
|
|
db.adminTelephonyPolicy.findUnique({
|
|
where: { adminUserId: context.adminUserId },
|
|
}),
|
|
db.adminDialerList.findMany({
|
|
where: {
|
|
...ownerWhere,
|
|
archivedAt: null,
|
|
},
|
|
orderBy: [{ updatedAt: "desc" }, { id: "asc" }],
|
|
take: 50,
|
|
include: {
|
|
entries: {
|
|
orderBy: [{ position: "asc" }, { id: "asc" }],
|
|
include: {
|
|
contact: {
|
|
select: {
|
|
id: true,
|
|
firstName: true,
|
|
lastName: true,
|
|
normalizedPhone: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}),
|
|
]);
|
|
const foreignCalls = calls.filter(
|
|
(call) => call.adminUserId !== context.adminUserId
|
|
);
|
|
if (foreignCalls.length > 0) {
|
|
await writeAdminSalesAudit(db, {
|
|
actor: context.actor("Fremde Team-Anrufe aufgelistet"),
|
|
action: "admin.sales.telephony.foreignCalls.read",
|
|
targetType: "AdminCallSession",
|
|
targetId: foreignCalls[0]!.id,
|
|
after: {
|
|
foreignTargets: foreignCalls.map((call) => ({
|
|
targetId: call.id,
|
|
dataOwnerAdminUserId: call.adminUserId,
|
|
})),
|
|
accessKind: "read",
|
|
},
|
|
});
|
|
}
|
|
const selectedCall = selectedCallId
|
|
? calls.find((call) => call.id === selectedCallId)
|
|
: null;
|
|
if (
|
|
selectedCall &&
|
|
selectedCall.adminUserId !== context.adminUserId
|
|
) {
|
|
await writeAdminSalesAudit(db, {
|
|
actor: context.actor("Fremden Team-Anruf geöffnet"),
|
|
action: "admin.sales.telephony.foreignCall.read",
|
|
targetType: "AdminCallSession",
|
|
targetId: selectedCall.id,
|
|
after: {
|
|
dataOwnerAdminUserId: selectedCall.adminUserId,
|
|
accessKind: "read",
|
|
},
|
|
});
|
|
}
|
|
|
|
return {
|
|
calls,
|
|
selectedCallId,
|
|
dispositions,
|
|
contacts,
|
|
policy,
|
|
dialerLists,
|
|
permissions: {
|
|
canCall: context.canCall,
|
|
canManageSettings: context.canManageSettings,
|
|
canManageRecording: context.canManageRecording,
|
|
},
|
|
};
|
|
}
|
|
|
|
export async function applyAdminTelephonyWorkspaceAction(
|
|
context: AdminTelephonyOperatorContext,
|
|
action: AdminTelephonyAction
|
|
) {
|
|
const actor = context.actor(action.reason);
|
|
switch (action.intent) {
|
|
case "save-outcome":
|
|
return context.actions.saveOutcome({
|
|
adminUserId: context.adminUserId,
|
|
callSessionId: action.callSessionId,
|
|
dispositionCode: action.dispositionCode,
|
|
note: action.note,
|
|
callbackTask:
|
|
action.callbackTitle && action.callbackDueAt
|
|
? {
|
|
title: action.callbackTitle,
|
|
dueAt: new Date(action.callbackDueAt),
|
|
timeZone: "Europe/Berlin",
|
|
priority: "high",
|
|
}
|
|
: null,
|
|
actor,
|
|
});
|
|
case "confirm-match":
|
|
return context.actions.confirmMatch({
|
|
adminUserId: context.adminUserId,
|
|
callSessionId: action.callSessionId,
|
|
contactId: action.contactId,
|
|
leadId: action.leadId,
|
|
dealId: action.dealId,
|
|
actor,
|
|
});
|
|
case "queue-call":
|
|
return context.actions.queueOutboundCall({
|
|
adminUserId: context.adminUserId,
|
|
contactId: action.contactId,
|
|
dealId: action.dealId,
|
|
dialerEntryId: action.dialerEntryId,
|
|
idempotencyKey: action.idempotencyKey,
|
|
actor,
|
|
});
|
|
case "create-dialer":
|
|
return context.actions.createDialerList({
|
|
adminUserId: context.adminUserId,
|
|
name: action.name,
|
|
contactIds: action.contactIds,
|
|
actor,
|
|
});
|
|
case "next-dialer":
|
|
return context.actions.activateNextDialerEntry({
|
|
adminUserId: context.adminUserId,
|
|
dialerListId: action.dialerListId,
|
|
actor,
|
|
});
|
|
case "finish-dialer":
|
|
return context.actions.finishDialerEntry({
|
|
adminUserId: context.adminUserId,
|
|
dialerEntryId: action.dialerEntryId,
|
|
outcome: action.outcome,
|
|
callSessionId: action.callSessionId,
|
|
actor,
|
|
});
|
|
case "update-policy":
|
|
return context.actions.updatePolicy({
|
|
adminUserId: context.adminUserId,
|
|
outboundEnabled: action.outboundEnabled,
|
|
dailyCallLimit: action.dailyCallLimit,
|
|
workDayStartMinutes: action.workDayStartMinutes,
|
|
workDayEndMinutes: action.workDayEndMinutes,
|
|
workDays: action.workDays,
|
|
timeZone: action.timeZone,
|
|
dncEnforced: true,
|
|
actor,
|
|
});
|
|
case "update-recording-policy":
|
|
return context.recordings.updatePolicy({
|
|
adminUserId: context.adminUserId,
|
|
enabled: action.enabled,
|
|
accepted: action.accepted,
|
|
consentText: action.consentText,
|
|
consentVersion: action.consentVersion,
|
|
actor,
|
|
});
|
|
case "confirm-recording-consent":
|
|
return context.recordings.confirmCallConsent({
|
|
adminUserId: context.adminUserId,
|
|
callSessionId: action.callSessionId,
|
|
confirmed: action.confirmed,
|
|
actor,
|
|
});
|
|
}
|
|
}
|