Add standalone Leads module for prospect outreach before customers.

Import large Excel lists with duplicate checks, assign and kanban follow-up, convert to Person when ready; also fix OTP mobile mask order and SMS brand name to منتکت.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-27 17:05:11 +03:30
co-authored by Cursor
parent 22980e5152
commit c079295ff4
24 changed files with 4357 additions and 9 deletions
+211 -1
View File
@@ -124,7 +124,8 @@ export type OrgModuleKey =
| "teams"
| "woocommerce"
| "voip"
| "reservations";
| "reservations"
| "leads";
export const ORG_MODULE_KEYS: OrgModuleKey[] = [
"customers",
@@ -139,6 +140,7 @@ export const ORG_MODULE_KEYS: OrgModuleKey[] = [
"workflow_automations",
"message_automations",
"lead_forms",
"leads",
"project_boards",
"inventory",
"cheques",
@@ -174,6 +176,7 @@ export const ORG_MODULE_LABELS: Record<OrgModuleKey, string> = {
workflow_automations: "اتوماسیون گردش کار",
message_automations: "اتوماسیون پیامک",
lead_forms: "�رم‌ساز سرنخ",
leads: "مدیریت لیدها",
project_boards: "کاریز پروژه",
inventory: "انبار",
cheques: "چک‌ها",
@@ -205,6 +208,7 @@ export const ORG_MODULE_GROUPS: Array<{
title: "�روش",
keys: [
"pipeline",
"leads",
"products",
"invoices",
"quotes",
@@ -3657,3 +3661,209 @@ export interface ReservationListResponse {
items: ReservationAppointmentDto[];
total: number;
}
/* ——— ãÏíÑíÊ áíÏåÇ ——— */
export type LeadAssignRule = "round_robin" | "equal_split";
export interface LeadStageDto {
id: string;
name: string;
position: number;
color: string;
showOnBoard: boolean;
isConverted: boolean;
isLost: boolean;
isUnqualified: boolean;
leadCount?: number;
}
export interface CreateLeadStageRequest {
name: string;
color?: string;
showOnBoard?: boolean;
isConverted?: boolean;
isLost?: boolean;
isUnqualified?: boolean;
}
export interface UpdateLeadStageRequest {
name?: string;
color?: string;
showOnBoard?: boolean;
isConverted?: boolean;
isLost?: boolean;
isUnqualified?: boolean;
position?: number;
}
export interface LeadDto {
id: string;
title: string;
contactName: string | null;
mobile: string;
phone: string | null;
city: string | null;
region: string | null;
trade: string | null;
source: string | null;
notes: string | null;
estimatedValue: number | null;
stageId: string;
stageName: string;
position: number;
assigneeId: string | null;
assigneeName: string | null;
lastContactAt: string | null;
nextFollowUpAt: string | null;
convertedPersonId: string | null;
convertedAt: string | null;
archivedAt: string | null;
daysInStage: number;
createdAt: string;
updatedAt: string;
}
export interface LeadEventDto {
id: string;
type: string;
title: string;
body: string | null;
actorId: string | null;
createdAt: string;
}
export interface CreateLeadRequest {
title: string;
mobile: string;
contactName?: string | null;
phone?: string | null;
city?: string | null;
region?: string | null;
trade?: string | null;
source?: string | null;
notes?: string | null;
estimatedValue?: number | null;
stageId?: string;
assigneeId?: string | null;
nextFollowUpAt?: string | null;
}
export interface UpdateLeadRequest {
title?: string;
mobile?: string;
contactName?: string | null;
phone?: string | null;
city?: string | null;
region?: string | null;
trade?: string | null;
source?: string | null;
notes?: string | null;
estimatedValue?: number | null;
stageId?: string;
assigneeId?: string | null;
nextFollowUpAt?: string | null;
lastContactAt?: string | null;
}
export interface MoveLeadRequest {
stageId: string;
position?: number;
}
export interface AssignLeadsRequest {
leadIds: string[];
assigneeId: string;
}
export interface BulkLeadStageRequest {
leadIds: string[];
stageId: string;
}
export interface AutoAssignLeadsRequest {
rule?: LeadAssignRule;
leadIds?: string[];
assigneeIds?: string[];
onlyUnassigned?: boolean;
}
export interface LeadCallLogRequest {
outcome?: string;
body?: string;
nextFollowUpAt?: string | null;
stageId?: string;
}
export interface LeadNoteRequest {
body: string;
}
export interface LeadFollowUpRequest {
nextFollowUpAt: string;
body?: string;
}
export interface ConvertLeadRequest {
firstName?: string;
lastName?: string;
createIfMissing?: boolean;
}
export interface ConvertLeadResult {
lead: LeadDto;
personId: string;
createdPerson: boolean;
}
export interface LeadDuplicateCheckResult {
exists: boolean;
kind: "lead" | "person" | null;
id: string | null;
title: string | null;
}
export interface LeadImportJobDto {
id: string;
fileName: string | null;
status: string;
totalCount: number;
processedCount: number;
successCount: number;
duplicateCount: number;
errorCount: number;
errorSamples: string[];
startedAt: string | null;
finishedAt: string | null;
createdAt: string;
}
export interface LeadImportRow {
title?: string;
contactName?: string;
mobile?: string;
phone?: string;
city?: string;
region?: string;
trade?: string;
source?: string;
notes?: string;
}
export interface LeadStatsDto {
total: number;
newCount: number;
unassigned: number;
byAssignee: Array<{ userId: string; fullName: string; count: number }>;
callsLogged: number;
withFollowUp: number;
interested: number;
converted: number;
lost: number;
conversionRate: number;
}
export interface LeadBoardColumnDto {
stage: LeadStageDto;
leads: LeadDto[];
}