133 lines
4.3 KiB
TypeScript
133 lines
4.3 KiB
TypeScript
import type { Doc } from "./_generated/dataModel"
|
|
|
|
export type AutomationTrigger = "TICKET_CREATED" | "STATUS_CHANGED" | "COMMENT_ADDED" | "TICKET_RESOLVED"
|
|
|
|
export type AutomationConditionOperator = "AND" | "OR"
|
|
|
|
export type AutomationConditionField =
|
|
| "companyId"
|
|
| "queueId"
|
|
| "categoryId"
|
|
| "subcategoryId"
|
|
| "priority"
|
|
| "status"
|
|
| "channel"
|
|
| "formTemplate"
|
|
| "chatEnabled"
|
|
| "tag"
|
|
|
|
export type AutomationConditionComparator =
|
|
| "eq"
|
|
| "neq"
|
|
| "in"
|
|
| "not_in"
|
|
| "contains"
|
|
| "not_contains"
|
|
| "is_true"
|
|
| "is_false"
|
|
|
|
export type AutomationCondition = {
|
|
field: AutomationConditionField
|
|
op: AutomationConditionComparator
|
|
value?: unknown
|
|
}
|
|
|
|
export type AutomationConditionGroup = {
|
|
op: AutomationConditionOperator
|
|
conditions: AutomationCondition[]
|
|
}
|
|
|
|
export type TicketForAutomation = Pick<
|
|
Doc<"tickets">,
|
|
| "tenantId"
|
|
| "status"
|
|
| "priority"
|
|
| "channel"
|
|
| "queueId"
|
|
| "companyId"
|
|
| "categoryId"
|
|
| "subcategoryId"
|
|
| "tags"
|
|
| "formTemplate"
|
|
| "chatEnabled"
|
|
>
|
|
|
|
function normalizeId(value: unknown): string | null {
|
|
if (!value) return null
|
|
return String(value)
|
|
}
|
|
|
|
function normalizeString(value: unknown): string | null {
|
|
if (typeof value !== "string") return null
|
|
const trimmed = value.trim()
|
|
return trimmed.length > 0 ? trimmed : null
|
|
}
|
|
|
|
function normalizeStringArray(value: unknown): string[] {
|
|
if (!Array.isArray(value)) return []
|
|
return value.map((item) => normalizeString(item)).filter((item): item is string => Boolean(item))
|
|
}
|
|
|
|
function normalizeBoolean(value: unknown): boolean | null {
|
|
if (typeof value === "boolean") return value
|
|
return null
|
|
}
|
|
|
|
function compareValue(actual: string | null, op: AutomationConditionComparator, expected: unknown): boolean {
|
|
if (op === "eq") return actual === normalizeId(expected)
|
|
if (op === "neq") return actual !== normalizeId(expected)
|
|
if (op === "in") {
|
|
const list = normalizeStringArray(expected).map((v) => v)
|
|
return actual !== null && list.includes(actual)
|
|
}
|
|
if (op === "not_in") {
|
|
const list = normalizeStringArray(expected).map((v) => v)
|
|
return actual === null || !list.includes(actual)
|
|
}
|
|
return false
|
|
}
|
|
|
|
export function evaluateAutomationConditions(
|
|
ticket: TicketForAutomation,
|
|
group: AutomationConditionGroup | null | undefined
|
|
): boolean {
|
|
if (!group || !Array.isArray(group.conditions) || group.conditions.length === 0) return true
|
|
const op = group.op === "OR" ? "OR" : "AND"
|
|
|
|
const results = group.conditions.map((condition) => {
|
|
const field = condition.field
|
|
const operator = condition.op
|
|
|
|
if (field === "companyId") return compareValue(normalizeId(ticket.companyId), operator, condition.value)
|
|
if (field === "queueId") return compareValue(normalizeId(ticket.queueId), operator, condition.value)
|
|
if (field === "categoryId") return compareValue(normalizeId(ticket.categoryId), operator, condition.value)
|
|
if (field === "subcategoryId") return compareValue(normalizeId(ticket.subcategoryId), operator, condition.value)
|
|
if (field === "priority") return compareValue(normalizeString(ticket.priority), operator, condition.value)
|
|
if (field === "status") return compareValue(normalizeString(ticket.status), operator, condition.value)
|
|
if (field === "channel") return compareValue(normalizeString(ticket.channel), operator, condition.value)
|
|
if (field === "formTemplate") return compareValue(normalizeString(ticket.formTemplate), operator, condition.value)
|
|
|
|
if (field === "chatEnabled") {
|
|
const expectedBool = normalizeBoolean(condition.value)
|
|
if (operator === "is_true") return ticket.chatEnabled === true
|
|
if (operator === "is_false") return ticket.chatEnabled !== true
|
|
if (operator === "eq") return expectedBool !== null ? Boolean(ticket.chatEnabled) === expectedBool : false
|
|
if (operator === "neq") return expectedBool !== null ? Boolean(ticket.chatEnabled) !== expectedBool : false
|
|
return false
|
|
}
|
|
|
|
if (field === "tag") {
|
|
const tag = normalizeString(condition.value)
|
|
if (!tag) return false
|
|
const has = (ticket.tags ?? []).includes(tag)
|
|
if (operator === "contains" || operator === "eq") return has
|
|
if (operator === "not_contains" || operator === "neq") return !has
|
|
return false
|
|
}
|
|
|
|
return false
|
|
})
|
|
|
|
return op === "OR" ? results.some(Boolean) : results.every(Boolean)
|
|
}
|
|
|