Let automations fire on more CRM events and open the studio from the main nav.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-09 21:43:17 +03:30
co-authored by Cursor
parent 51684daa61
commit f1aba1b386
20 changed files with 419 additions and 49 deletions
+69 -16
View File
@@ -32,7 +32,7 @@ import { SmsService } from '../common/sms.service';
export type AutomationContext = {
organizationId: string;
entityType: 'deal' | 'ticket';
entityType: 'deal' | 'ticket' | 'person' | 'invoice' | 'activity';
entityId: string;
title: string;
personId?: string | null;
@@ -182,15 +182,65 @@ export class AutomationsService {
ticketPriority: null,
};
}
const ticket = await this.prisma.ticket.findFirst({
where: { id: ctx.entityId, organizationId: ctx.organizationId },
select: { priority: true, personId: true, companyId: true },
});
if (ctx.entityType === 'ticket') {
const ticket = await this.prisma.ticket.findFirst({
where: { id: ctx.entityId, organizationId: ctx.organizationId },
select: { priority: true, personId: true, companyId: true },
});
return {
amount: null,
hasPerson: Boolean(ticket?.personId ?? ctx.personId),
hasCompany: Boolean(ticket?.companyId ?? ctx.companyId),
ticketPriority: ticket?.priority ?? null,
};
}
if (ctx.entityType === 'invoice') {
const invoice = await this.prisma.invoice.findFirst({
where: { id: ctx.entityId, organizationId: ctx.organizationId },
select: {
personId: true,
companyId: true,
discountAmount: true,
taxPercent: true,
lineItems: { select: { quantity: true, unitPrice: true } },
},
});
let amount: number | null = null;
if (invoice) {
const subtotal = invoice.lineItems.reduce(
(sum, item) => sum + Number(item.quantity) * Number(item.unitPrice),
0,
);
const discount = Number(invoice.discountAmount ?? 0);
const taxPercent = Number(invoice.taxPercent ?? 0);
amount = Math.abs(
subtotal - discount + Math.max(0, subtotal - discount) * (taxPercent / 100),
);
}
return {
amount,
hasPerson: Boolean(invoice?.personId ?? ctx.personId),
hasCompany: Boolean(invoice?.companyId ?? ctx.companyId),
ticketPriority: null,
};
}
if (ctx.entityType === 'activity') {
const activity = await this.prisma.activity.findFirst({
where: { id: ctx.entityId, organizationId: ctx.organizationId },
select: { personId: true, companyId: true, dealId: true },
});
return {
amount: null,
hasPerson: Boolean(activity?.personId ?? ctx.personId),
hasCompany: Boolean(activity?.companyId ?? ctx.companyId),
ticketPriority: null,
};
}
return {
amount: null,
hasPerson: Boolean(ticket?.personId ?? ctx.personId),
hasCompany: Boolean(ticket?.companyId ?? ctx.companyId),
ticketPriority: ticket?.priority ?? null,
hasPerson: Boolean(ctx.personId),
hasCompany: Boolean(ctx.companyId),
ticketPriority: null,
};
}
@@ -227,10 +277,7 @@ export class AutomationsService {
type: 'automation',
title: 'فعالیت خودکار',
body: title,
link:
ctx.entityType === 'deal'
? `/deals/${ctx.entityId}`
: `/tickets/${ctx.entityId}`,
link: entityLink(ctx),
entityType: ctx.entityType,
entityId: ctx.entityId,
});
@@ -263,10 +310,7 @@ export class AutomationsService {
const wantApp = channel === 'app' || channel === 'both';
const wantSms = channel === 'sms' || channel === 'both';
const smsText = (config.body?.trim() || title).slice(0, 160);
const link =
ctx.entityType === 'deal'
? `/deals/${ctx.entityId}`
: `/tickets/${ctx.entityId}`;
const link = entityLink(ctx);
if (config.notifyTarget === 'person') {
if (wantSms) {
@@ -413,3 +457,12 @@ function defaultTitle(action: AutomationAction, entityTitle: string) {
if (action === 'create_notification') return `اتوماسیون: ${entityTitle}`;
return entityTitle;
}
function entityLink(ctx: AutomationContext) {
if (ctx.entityType === 'deal') return `/deals/${ctx.entityId}`;
if (ctx.entityType === 'ticket') return `/tickets/${ctx.entityId}`;
if (ctx.entityType === 'invoice') return `/invoices/${ctx.entityId}`;
if (ctx.entityType === 'person') return `/contacts/people/${ctx.entityId}`;
if (ctx.entityType === 'activity') return `/activities/${ctx.entityId}`;
return '/';
}
+13 -1
View File
@@ -12,7 +12,19 @@ import {
ValidateIf,
} from 'class-validator';
const TRIGGERS = ['deal_won', 'ticket_resolved'] as const;
const TRIGGERS = [
'person_created',
'lead_submitted',
'deal_created',
'deal_stage_changed',
'deal_won',
'deal_lost',
'ticket_created',
'ticket_resolved',
'invoice_issued',
'invoice_paid',
'activity_done',
] as const;
const ACTIONS = [
'create_activity',
'create_notification',