fix(leads): give a new form lead the same owner as its first call
When a form has no round-robin assignees, the lead now follows the person the automation assigns to the first call. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -7,6 +7,19 @@ export type TemplateVars = {
|
||||
formValues?: Record<string, unknown> | null;
|
||||
};
|
||||
|
||||
/**
|
||||
* مسئول سرنخ فقط از اولین فعالیتی که مسئول دارد پر میشود.
|
||||
* اگر قبلاً گرفته شده یا این فعالیت مسئول ندارد، null برمیگردد.
|
||||
*/
|
||||
export function claimLeadAssignee(
|
||||
alreadyClaimed: boolean,
|
||||
activityAssigneeId: string | null | undefined,
|
||||
): string | null {
|
||||
if (alreadyClaimed) return null;
|
||||
const id = activityAssigneeId?.trim();
|
||||
return id || null;
|
||||
}
|
||||
|
||||
/** counter یکواحد افزایشیافته از AutomationCursor است (از ۱ شروع میشود) */
|
||||
export function pickRoundRobin(memberIds: string[], counter: number): string | null {
|
||||
const ids = [...new Set(memberIds.filter(Boolean))].sort();
|
||||
|
||||
@@ -34,6 +34,7 @@ import { NotificationsService } from '../notifications/notifications.service';
|
||||
import { LoyaltyService } from '../loyalty/loyalty.service';
|
||||
import { SmsGatewayService } from '../sms-billing/sms-gateway.service';
|
||||
import {
|
||||
claimLeadAssignee,
|
||||
formatFormAnswers,
|
||||
pickRoundRobin,
|
||||
renderAutomationTemplate,
|
||||
@@ -59,6 +60,12 @@ export type AutomationContext = {
|
||||
formName?: string | null;
|
||||
formValues?: Record<string, unknown> | null;
|
||||
formFields?: FormFieldLabel[] | null;
|
||||
/**
|
||||
* فرم سرنخ مسئول نوبتی ندارد: مسئول سرنخ همان مسئول اولین فعالیت این اجرا میشود.
|
||||
* داخل همان اجرا یکبار ست میشود تا فعالیت بعدی مسئول را عوض نکند.
|
||||
*/
|
||||
syncLeadAssignee?: boolean;
|
||||
leadAssigneeSynced?: boolean;
|
||||
};
|
||||
|
||||
type AutomationFacts = {
|
||||
@@ -406,6 +413,54 @@ export class AutomationsService {
|
||||
entityId: activity.id,
|
||||
});
|
||||
}
|
||||
await this.syncLeadAssigneeFromActivity(ctx, type === 'call' ? assigneeId : null);
|
||||
}
|
||||
|
||||
/** سرنخ تازهای که فرم برایش مسئول نوبتی ندارد، مسئول اولین تماس را میگیرد. */
|
||||
private async syncLeadAssigneeFromActivity(
|
||||
ctx: AutomationContext,
|
||||
activityAssigneeId: string | null,
|
||||
) {
|
||||
const assigneeId = claimLeadAssignee(ctx.leadAssigneeSynced === true, activityAssigneeId);
|
||||
if (!ctx.syncLeadAssignee || !assigneeId || !ctx.leadId) return;
|
||||
|
||||
const existing = await this.prisma.lead.findFirst({
|
||||
where: {
|
||||
id: ctx.leadId,
|
||||
organizationId: ctx.organizationId,
|
||||
archivedAt: null,
|
||||
},
|
||||
select: { assigneeId: true },
|
||||
});
|
||||
if (!existing) return;
|
||||
|
||||
ctx.leadAssigneeSynced = true;
|
||||
ctx.assigneeId = assigneeId;
|
||||
if (existing.assigneeId === assigneeId) return;
|
||||
|
||||
await this.prisma.lead.updateMany({
|
||||
where: {
|
||||
id: ctx.leadId,
|
||||
organizationId: ctx.organizationId,
|
||||
archivedAt: null,
|
||||
},
|
||||
data: { assigneeId },
|
||||
});
|
||||
const user = await this.prisma.user.findFirst({
|
||||
where: { id: assigneeId },
|
||||
select: { fullName: true },
|
||||
});
|
||||
const name = user?.fullName?.trim() || 'کارشناس';
|
||||
await this.prisma.leadEvent.create({
|
||||
data: {
|
||||
organizationId: ctx.organizationId,
|
||||
leadId: ctx.leadId,
|
||||
type: 'assigned',
|
||||
title: `به «${name}» اختصاص داده شد`,
|
||||
body: 'مسئول سرنخ همان مسئول اولین تماس اتوماسیون شد.',
|
||||
meta: { assigneeId, source: 'automation' },
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private async resolveAssignee(
|
||||
|
||||
@@ -391,6 +391,7 @@ export class LeadFormsService {
|
||||
sourceId: form.id,
|
||||
sourceType: 'lead_form',
|
||||
leadId,
|
||||
syncLeadAssignee: !ownerId && leadCreated && !!leadId,
|
||||
mobile: mapped.mobile ?? mapped.phone ?? null,
|
||||
formName: form.name,
|
||||
formValues: values,
|
||||
|
||||
Reference in New Issue
Block a user