feat(public-api): let external sites register a lead with their key
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1170,6 +1170,98 @@ export class LeadsService {
|
||||
};
|
||||
}
|
||||
|
||||
async createFromExternal(input: {
|
||||
organizationId: string;
|
||||
siteName: string;
|
||||
firstName?: string | null;
|
||||
lastName?: string | null;
|
||||
mobile: string;
|
||||
storeName?: string | null;
|
||||
source?: string | null;
|
||||
}): Promise<{
|
||||
ok: true;
|
||||
created: boolean;
|
||||
duplicate: boolean;
|
||||
leadId: string | null;
|
||||
}> {
|
||||
const mobile = mobileKey(input.mobile);
|
||||
if (!mobile || !/^09\d{9}$/.test(mobile)) {
|
||||
throw new BadRequestException('شماره موبایل معتبر نیست');
|
||||
}
|
||||
|
||||
const contactName =
|
||||
`${input.firstName?.trim() ?? ''} ${input.lastName?.trim() ?? ''}`.trim() || null;
|
||||
const storeName = input.storeName?.trim() ?? '';
|
||||
const source = emptyToNull(input.source);
|
||||
const title = storeName || contactName || mobile;
|
||||
const siteName = input.siteName.trim() || 'سایت بیرونی';
|
||||
|
||||
const existing = await this.prisma.lead.findFirst({
|
||||
where: { organizationId: input.organizationId, mobile, archivedAt: null },
|
||||
select: { id: true, source: true, contactName: true },
|
||||
});
|
||||
if (existing) {
|
||||
const patch: Prisma.LeadUpdateInput = {};
|
||||
if (!existing.source && source) patch.source = source;
|
||||
if (!existing.contactName && contactName) patch.contactName = contactName;
|
||||
if (Object.keys(patch).length) {
|
||||
await this.prisma.lead.update({ where: { id: existing.id }, data: patch });
|
||||
}
|
||||
await this.prisma.leadEvent.create({
|
||||
data: {
|
||||
organizationId: input.organizationId,
|
||||
leadId: existing.id,
|
||||
type: 'note',
|
||||
title: 'درخواست دوباره از سایت',
|
||||
body: [`سایت: ${siteName}`, source ? `منبع: ${source}` : null]
|
||||
.filter(Boolean)
|
||||
.join('\n'),
|
||||
},
|
||||
});
|
||||
return { ok: true, created: false, duplicate: true, leadId: existing.id };
|
||||
}
|
||||
|
||||
const person = await this.prisma.person.findFirst({
|
||||
where: {
|
||||
organizationId: input.organizationId,
|
||||
OR: [{ mobile }, { phone: mobile }],
|
||||
},
|
||||
select: { id: true },
|
||||
});
|
||||
if (person) {
|
||||
return { ok: true, created: false, duplicate: true, leadId: null };
|
||||
}
|
||||
|
||||
await this.stages.ensureDefaults(input.organizationId);
|
||||
const stages = await this.stages.list(input.organizationId);
|
||||
const stageId = stages[0]?.id;
|
||||
if (!stageId) throw new BadRequestException('مرحلهای تعریف نشده');
|
||||
|
||||
const lead = await this.prisma.lead.create({
|
||||
data: {
|
||||
organizationId: input.organizationId,
|
||||
title,
|
||||
contactName,
|
||||
mobile,
|
||||
source,
|
||||
notes: `ثبت از سایت: ${siteName}`,
|
||||
stageId,
|
||||
},
|
||||
});
|
||||
await this.prisma.leadEvent.create({
|
||||
data: {
|
||||
organizationId: input.organizationId,
|
||||
leadId: lead.id,
|
||||
type: 'created',
|
||||
title: 'لید از سایت ثبت شد',
|
||||
body: [`سایت: ${siteName}`, source ? `منبع: ${source}` : null]
|
||||
.filter(Boolean)
|
||||
.join('\n'),
|
||||
},
|
||||
});
|
||||
return { ok: true, created: true, duplicate: false, leadId: lead.id };
|
||||
}
|
||||
|
||||
async createFromImportRow(
|
||||
organizationId: string,
|
||||
createdById: string | null,
|
||||
|
||||
Reference in New Issue
Block a user