Give automations a visual chain with conditions and SMS, and match the pipeline board to the sales mockup.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1739,13 +1739,123 @@ export const AUTOMATION_ACTION_LABELS: Record<AutomationAction, string> = {
|
||||
add_loyalty_points: "افزودن امتیاز باشگاه",
|
||||
};
|
||||
|
||||
export const MAX_AUTOMATION_STEPS = 8;
|
||||
|
||||
export const AUTOMATION_ACTIONS = [
|
||||
"create_activity",
|
||||
"create_notification",
|
||||
"add_loyalty_points",
|
||||
] as const satisfies readonly AutomationAction[];
|
||||
|
||||
export const AUTOMATION_TRIGGERS = [
|
||||
"deal_won",
|
||||
"ticket_resolved",
|
||||
] as const satisfies readonly AutomationTrigger[];
|
||||
|
||||
export type AutomationStepKind = "action" | "condition";
|
||||
|
||||
export type AutomationConditionField =
|
||||
| "deal_amount"
|
||||
| "has_person"
|
||||
| "has_company"
|
||||
| "ticket_priority";
|
||||
|
||||
export type AutomationConditionOp =
|
||||
| "gte"
|
||||
| "lte"
|
||||
| "eq"
|
||||
| "neq"
|
||||
| "is_set"
|
||||
| "is_empty";
|
||||
|
||||
export type AutomationNotifyChannel = "app" | "sms" | "both";
|
||||
export type AutomationNotifyTarget = "staff" | "person";
|
||||
|
||||
export const AUTOMATION_CONDITION_FIELD_LABELS: Record<
|
||||
AutomationConditionField,
|
||||
string
|
||||
> = {
|
||||
deal_amount: "مبلغ معامله",
|
||||
has_person: "مخاطب دارد",
|
||||
has_company: "شرکت دارد",
|
||||
ticket_priority: "اولویت تیکت",
|
||||
};
|
||||
|
||||
export const AUTOMATION_CONDITION_OP_LABELS: Record<
|
||||
AutomationConditionOp,
|
||||
string
|
||||
> = {
|
||||
gte: "بزرگتر یا مساوی",
|
||||
lte: "کوچکتر یا مساوی",
|
||||
eq: "مساوی",
|
||||
neq: "نامساوی",
|
||||
is_set: "دارد",
|
||||
is_empty: "ندارد",
|
||||
};
|
||||
|
||||
export const AUTOMATION_NOTIFY_CHANNEL_LABELS: Record<
|
||||
AutomationNotifyChannel,
|
||||
string
|
||||
> = {
|
||||
app: "ناتیفیکیشن داخلی",
|
||||
sms: "پیامک",
|
||||
both: "هر دو",
|
||||
};
|
||||
|
||||
export const AUTOMATION_NOTIFY_CHANNELS = [
|
||||
"app",
|
||||
"sms",
|
||||
"both",
|
||||
] as const satisfies readonly AutomationNotifyChannel[];
|
||||
|
||||
export function isAutomationCondition(step: Pick<AutomationStep, "kind">): boolean {
|
||||
return step.kind === "condition";
|
||||
}
|
||||
|
||||
export function firstAutomationAction(
|
||||
steps: AutomationStep[],
|
||||
): AutomationAction | null {
|
||||
const step = steps.find((s) => !isAutomationCondition(s) && s.action);
|
||||
return step?.action ?? null;
|
||||
}
|
||||
|
||||
export function conditionFieldsForTrigger(
|
||||
trigger: AutomationTrigger,
|
||||
): AutomationConditionField[] {
|
||||
if (trigger === "deal_won") {
|
||||
return ["deal_amount", "has_person", "has_company"];
|
||||
}
|
||||
return ["ticket_priority", "has_person", "has_company"];
|
||||
}
|
||||
|
||||
export interface AutomationStep {
|
||||
id: string;
|
||||
kind?: AutomationStepKind;
|
||||
action?: AutomationAction;
|
||||
title?: string;
|
||||
body?: string;
|
||||
assigneeId?: string | null;
|
||||
notifyUserId?: string | null;
|
||||
notifyChannel?: AutomationNotifyChannel;
|
||||
notifyTarget?: AutomationNotifyTarget;
|
||||
activityType?: "call" | "meeting" | "task" | "note";
|
||||
points?: number;
|
||||
conditionField?: AutomationConditionField;
|
||||
conditionOp?: AutomationConditionOp;
|
||||
conditionValue?: string | number;
|
||||
}
|
||||
|
||||
export interface AutomationRuleConfig {
|
||||
title?: string;
|
||||
body?: string;
|
||||
assigneeId?: string | null;
|
||||
notifyUserId?: string | null;
|
||||
notifyChannel?: AutomationNotifyChannel;
|
||||
notifyTarget?: AutomationNotifyTarget;
|
||||
activityType?: "call" | "meeting" | "task" | "note";
|
||||
points?: number;
|
||||
/** زنجیرهٔ اعمال؛ اگر خالی باشد همان action تکی استفاده میشود */
|
||||
steps?: AutomationStep[];
|
||||
}
|
||||
|
||||
export interface AutomationRuleDto {
|
||||
@@ -1769,6 +1879,77 @@ export interface CreateAutomationRuleRequest {
|
||||
|
||||
export type UpdateAutomationRuleRequest = Partial<CreateAutomationRuleRequest>;
|
||||
|
||||
export function normalizeAutomationSteps(
|
||||
action: AutomationAction,
|
||||
config?: AutomationRuleConfig | null,
|
||||
): AutomationStep[] {
|
||||
const cfg = config ?? {};
|
||||
if (Array.isArray(cfg.steps) && cfg.steps.length) {
|
||||
return cfg.steps
|
||||
.filter((s) => s && (s.kind === "condition" || s.action))
|
||||
.map((s, i) => ({
|
||||
id: s.id || `step-${i}`,
|
||||
kind: s.kind === "condition" ? "condition" : "action",
|
||||
action: s.action,
|
||||
title: s.title,
|
||||
body: s.body,
|
||||
assigneeId: s.assigneeId,
|
||||
notifyUserId: s.notifyUserId,
|
||||
notifyChannel: s.notifyChannel,
|
||||
notifyTarget: s.notifyTarget,
|
||||
activityType: s.activityType,
|
||||
points: s.points,
|
||||
conditionField: s.conditionField,
|
||||
conditionOp: s.conditionOp,
|
||||
conditionValue: s.conditionValue,
|
||||
}));
|
||||
}
|
||||
return [
|
||||
{
|
||||
id: "step-0",
|
||||
kind: "action",
|
||||
action,
|
||||
title: cfg.title,
|
||||
body: cfg.body,
|
||||
assigneeId: cfg.assigneeId,
|
||||
notifyUserId: cfg.notifyUserId,
|
||||
notifyChannel: cfg.notifyChannel,
|
||||
notifyTarget: cfg.notifyTarget,
|
||||
activityType: cfg.activityType,
|
||||
points: cfg.points,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function packAutomationConfig(steps: AutomationStep[]): AutomationRuleConfig {
|
||||
const first =
|
||||
steps.find((s) => (s.kind ?? "action") !== "condition") ?? steps[0];
|
||||
return {
|
||||
steps,
|
||||
title: first?.title,
|
||||
body: first?.body,
|
||||
assigneeId: first?.assigneeId,
|
||||
notifyUserId: first?.notifyUserId,
|
||||
notifyChannel: first?.notifyChannel,
|
||||
notifyTarget: first?.notifyTarget,
|
||||
activityType: first?.activityType,
|
||||
points: first?.points,
|
||||
};
|
||||
}
|
||||
|
||||
export function automationStepToConfig(step: AutomationStep): AutomationRuleConfig {
|
||||
return {
|
||||
title: step.title,
|
||||
body: step.body,
|
||||
assigneeId: step.assigneeId,
|
||||
notifyUserId: step.notifyUserId,
|
||||
notifyChannel: step.notifyChannel,
|
||||
notifyTarget: step.notifyTarget,
|
||||
activityType: step.activityType,
|
||||
points: step.points,
|
||||
};
|
||||
}
|
||||
|
||||
// —— تهحساب / حسابداری ——
|
||||
|
||||
export const TAHSAB_SANAD_TYPES = [
|
||||
|
||||
Reference in New Issue
Block a user