Adding registration date and multiple addresses for contacts and leads.
This commit is contained in:
@@ -21,6 +21,8 @@ export class CreateLeadDto {
|
||||
@IsOptional() @IsString() phone?: string;
|
||||
@IsOptional() @IsString() city?: string;
|
||||
@IsOptional() @IsString() region?: string;
|
||||
@IsOptional() @IsString() instagram?: string;
|
||||
@IsOptional() @IsArray() @IsString({ each: true }) addresses?: string[];
|
||||
@IsOptional() @IsString() trade?: string;
|
||||
@IsOptional() @IsString() source?: string;
|
||||
@IsOptional() @IsString() notes?: string;
|
||||
@@ -28,6 +30,8 @@ export class CreateLeadDto {
|
||||
@IsOptional() @IsString() stageId?: string;
|
||||
@IsOptional() @IsString() assigneeId?: string | null;
|
||||
@IsOptional() @IsString() nextFollowUpAt?: string | null;
|
||||
/** فقط برای ایمپورت اکسل — تاریخ ثبت مبدأ */
|
||||
@IsOptional() @IsString() registeredAt?: string | null;
|
||||
}
|
||||
|
||||
export class UpdateLeadDto {
|
||||
@@ -37,6 +41,8 @@ export class UpdateLeadDto {
|
||||
@IsOptional() @IsString() phone?: string | null;
|
||||
@IsOptional() @IsString() city?: string | null;
|
||||
@IsOptional() @IsString() region?: string | null;
|
||||
@IsOptional() @IsString() instagram?: string | null;
|
||||
@IsOptional() @IsArray() @IsString({ each: true }) addresses?: string[];
|
||||
@IsOptional() @IsString() trade?: string | null;
|
||||
@IsOptional() @IsString() source?: string | null;
|
||||
@IsOptional() @IsString() notes?: string | null;
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { LeadImportJobDto } from '@mento/shared';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import {
|
||||
parseExcelMapping,
|
||||
parseJalaliDateTime,
|
||||
previewExcel,
|
||||
readMappedRows,
|
||||
type ExcelFieldOption,
|
||||
@@ -40,6 +41,23 @@ const LEAD_ALIASES: Record<string, string> = {
|
||||
region: 'region',
|
||||
منطقه: 'region',
|
||||
محله: 'region',
|
||||
address: 'address',
|
||||
آدرس: 'address',
|
||||
آدرساصلی: 'address',
|
||||
address2: 'address2',
|
||||
آدرس۲: 'address2',
|
||||
آدرس2: 'address2',
|
||||
آدرسدوم: 'address2',
|
||||
homeaddress: 'homeAddress',
|
||||
آدرسمنزل: 'homeAddress',
|
||||
آدرسخانه: 'homeAddress',
|
||||
instagram: 'instagram',
|
||||
اینستاگرام: 'instagram',
|
||||
پیجاینستاگرام: 'instagram',
|
||||
آیدیاینستاگرام: 'instagram',
|
||||
registeredat: 'registeredAt',
|
||||
تاریخثبت: 'registeredAt',
|
||||
تاریخثبتمشتری: 'registeredAt',
|
||||
trade: 'trade',
|
||||
صنف: 'trade',
|
||||
رسته: 'trade',
|
||||
@@ -58,6 +76,11 @@ const LEAD_FIELDS: ExcelFieldOption[] = [
|
||||
{ key: 'phone', label: 'تلفن' },
|
||||
{ key: 'city', label: 'شهر' },
|
||||
{ key: 'region', label: 'منطقه' },
|
||||
{ key: 'address', label: 'آدرس' },
|
||||
{ key: 'address2', label: 'آدرس ۲' },
|
||||
{ key: 'homeAddress', label: 'آدرس منزل' },
|
||||
{ key: 'instagram', label: 'اینستاگرام' },
|
||||
{ key: 'registeredAt', label: 'تاریخ ثبت' },
|
||||
{ key: 'trade', label: 'صنف' },
|
||||
{ key: 'source', label: 'منبع' },
|
||||
{ key: 'notes', label: 'توضیحات' },
|
||||
@@ -237,6 +260,19 @@ export class LeadsImportService {
|
||||
}
|
||||
return '';
|
||||
};
|
||||
const registeredRaw = get(
|
||||
'registeredAt',
|
||||
'تاریخ ثبت',
|
||||
'تاریخ ثبت مشتری',
|
||||
'registeredat',
|
||||
);
|
||||
const registeredAt =
|
||||
parseJalaliDateTime(registeredRaw)?.toISOString() ??
|
||||
(() => {
|
||||
if (!registeredRaw) return '';
|
||||
const d = new Date(registeredRaw);
|
||||
return Number.isNaN(d.getTime()) ? '' : d.toISOString();
|
||||
})();
|
||||
return {
|
||||
title: get('title', 'نام', 'نام فروشگاه', 'فروشگاه', 'shop', 'name'),
|
||||
contactName: get('contactName', 'مالک', 'مسئول', 'نام مالک', 'owner'),
|
||||
@@ -244,6 +280,11 @@ export class LeadsImportService {
|
||||
phone: get('phone', 'تلفن'),
|
||||
city: get('city', 'شهر'),
|
||||
region: get('region', 'منطقه', 'محله'),
|
||||
address: get('address', 'آدرس', 'آدرس اصلی'),
|
||||
address2: get('address2', 'آدرس ۲', 'آدرس2', 'آدرس دوم'),
|
||||
homeAddress: get('homeAddress', 'آدرس منزل', 'آدرس خانه'),
|
||||
instagram: get('instagram', 'اینستاگرام', 'پیج اینستاگرام', 'آی دی اینستاگرام'),
|
||||
registeredAt,
|
||||
trade: get('trade', 'صنف', 'رسته'),
|
||||
source: get('source', 'منبع', 'منبع لید'),
|
||||
notes: get('notes', 'توضیحات', 'یادداشت'),
|
||||
|
||||
@@ -20,6 +20,11 @@ import {
|
||||
type Actor,
|
||||
elevated,
|
||||
} from '../common/record-scope';
|
||||
import {
|
||||
asAddresses,
|
||||
collectAddresses,
|
||||
toJsonAddresses,
|
||||
} from '../common/custom-fields';
|
||||
import { emptyToNull, normalizePhone } from '../contacts/normalize';
|
||||
import { LocalFileStorage } from '../storage/local-file.storage';
|
||||
import { LeadStagesService } from './lead-stages.service';
|
||||
@@ -238,6 +243,8 @@ export class LeadsService {
|
||||
phone?: string | null;
|
||||
city?: string | null;
|
||||
region?: string | null;
|
||||
instagram?: string | null;
|
||||
addresses?: string[];
|
||||
trade?: string | null;
|
||||
source?: string | null;
|
||||
notes?: string | null;
|
||||
@@ -245,6 +252,7 @@ export class LeadsService {
|
||||
stageId?: string;
|
||||
assigneeId?: string | null;
|
||||
nextFollowUpAt?: string | null;
|
||||
registeredAt?: string | null;
|
||||
},
|
||||
): Promise<LeadDto> {
|
||||
await this.stages.ensureDefaults(actor.organizationId);
|
||||
@@ -271,6 +279,7 @@ export class LeadsService {
|
||||
assigneeId = actor.userId;
|
||||
}
|
||||
|
||||
const registeredAt = dto.registeredAt ? new Date(dto.registeredAt) : null;
|
||||
const row = await this.prisma.lead.create({
|
||||
data: {
|
||||
organizationId: actor.organizationId,
|
||||
@@ -280,6 +289,8 @@ export class LeadsService {
|
||||
phone: emptyToNull(dto.phone),
|
||||
city: emptyToNull(dto.city),
|
||||
region: emptyToNull(dto.region),
|
||||
instagram: emptyToNull(dto.instagram),
|
||||
addresses: toJsonAddresses(dto.addresses),
|
||||
trade: emptyToNull(dto.trade),
|
||||
source: emptyToNull(dto.source),
|
||||
notes: emptyToNull(dto.notes),
|
||||
@@ -287,6 +298,9 @@ export class LeadsService {
|
||||
dto.estimatedValue != null
|
||||
? new Prisma.Decimal(dto.estimatedValue)
|
||||
: null,
|
||||
...(registeredAt && !Number.isNaN(registeredAt.getTime())
|
||||
? { createdAt: registeredAt }
|
||||
: {}),
|
||||
stageId,
|
||||
assigneeId,
|
||||
createdById: actor.userId,
|
||||
@@ -351,6 +365,12 @@ export class LeadsService {
|
||||
...(dto.region !== undefined
|
||||
? { region: emptyToNull(dto.region as string) }
|
||||
: {}),
|
||||
...(dto.instagram !== undefined
|
||||
? { instagram: emptyToNull(dto.instagram as string) }
|
||||
: {}),
|
||||
...(dto.addresses !== undefined
|
||||
? { addresses: toJsonAddresses(dto.addresses) }
|
||||
: {}),
|
||||
...(dto.trade !== undefined
|
||||
? { trade: emptyToNull(dto.trade as string) }
|
||||
: {}),
|
||||
@@ -717,6 +737,8 @@ export class LeadsService {
|
||||
lastName,
|
||||
mobile: lead.mobile,
|
||||
phone: lead.phone,
|
||||
instagram: lead.instagram,
|
||||
addresses: toJsonAddresses(asAddresses(lead.addresses)),
|
||||
notes: [
|
||||
lead.notes,
|
||||
lead.city ? `شهر: ${lead.city}` : null,
|
||||
@@ -727,6 +749,7 @@ export class LeadsService {
|
||||
.filter(Boolean)
|
||||
.join('\n'),
|
||||
ownerId: lead.assigneeId ?? actor.userId,
|
||||
createdAt: lead.createdAt,
|
||||
},
|
||||
});
|
||||
createdPerson = true;
|
||||
@@ -884,6 +907,11 @@ export class LeadsService {
|
||||
phone?: string;
|
||||
city?: string;
|
||||
region?: string;
|
||||
address?: string;
|
||||
address2?: string;
|
||||
homeAddress?: string;
|
||||
instagram?: string;
|
||||
registeredAt?: string;
|
||||
trade?: string;
|
||||
source?: string;
|
||||
notes?: string;
|
||||
@@ -910,6 +938,7 @@ export class LeadsService {
|
||||
if (existingPerson) return 'duplicate';
|
||||
|
||||
try {
|
||||
const registeredAt = row.registeredAt ? new Date(row.registeredAt) : null;
|
||||
const lead = await this.prisma.lead.create({
|
||||
data: {
|
||||
organizationId,
|
||||
@@ -919,11 +948,18 @@ export class LeadsService {
|
||||
phone: emptyToNull(row.phone),
|
||||
city: emptyToNull(row.city),
|
||||
region: emptyToNull(row.region),
|
||||
instagram: emptyToNull(row.instagram),
|
||||
addresses: toJsonAddresses(
|
||||
collectAddresses(row.address, row.address2, row.homeAddress),
|
||||
),
|
||||
trade: emptyToNull(row.trade),
|
||||
source: emptyToNull(row.source) ?? 'ایمپورت اکسل',
|
||||
notes: emptyToNull(row.notes),
|
||||
stageId: defaultStageId,
|
||||
createdById,
|
||||
...(registeredAt && !Number.isNaN(registeredAt.getTime())
|
||||
? { createdAt: registeredAt }
|
||||
: {}),
|
||||
},
|
||||
});
|
||||
await this.prisma.leadEvent.create({
|
||||
@@ -990,6 +1026,8 @@ export class LeadsService {
|
||||
phone: row.phone,
|
||||
city: row.city,
|
||||
region: row.region,
|
||||
instagram: row.instagram,
|
||||
addresses: asAddresses(row.addresses),
|
||||
trade: row.trade,
|
||||
source: row.source,
|
||||
notes: row.notes,
|
||||
|
||||
Reference in New Issue
Block a user