Keep previous import source and code when a person is re-imported from another source
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
ALTER TABLE "Person" ADD COLUMN IF NOT EXISTS "previousImportSource" TEXT;
|
||||
ALTER TABLE "Person" ADD COLUMN IF NOT EXISTS "previousSourceRef" TEXT;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "Person_organizationId_previousSourceRef_idx" ON "Person"("organizationId", "previousSourceRef");
|
||||
|
||||
-- اشخاصی که قبلاً از دیدار آمدهاند و بعد با تهحساب بازنویسی شدهاند: کد دیدار را بهعنوان کد قبلی بازیابی کن
|
||||
UPDATE "Person"
|
||||
SET "previousImportSource" = 'didar',
|
||||
"previousSourceRef" = TRIM("customFields"->>'didarId')
|
||||
WHERE "importSource" = 'tahesab'
|
||||
AND "previousSourceRef" IS NULL
|
||||
AND "customFields" IS NOT NULL
|
||||
AND jsonb_typeof("customFields") = 'object'
|
||||
AND COALESCE(TRIM("customFields"->>'didarId'), '') <> '';
|
||||
@@ -441,6 +441,9 @@ model Person {
|
||||
importSource String @default("manual")
|
||||
/// شناسه در مبدأ (کد دیدار، کد مشتری تهحساب، …)
|
||||
sourceRef String?
|
||||
/// منبع و کد قبلی وقتی شخص از منبع دیگری دوباره وارد شده (مثلاً دیدار قبل از تهحساب)
|
||||
previousImportSource String?
|
||||
previousSourceRef String?
|
||||
email String?
|
||||
phone String?
|
||||
mobile String?
|
||||
@@ -488,6 +491,7 @@ model Person {
|
||||
@@index([organizationId, lastName, firstName])
|
||||
@@index([organizationId, importSource])
|
||||
@@index([organizationId, sourceRef])
|
||||
@@index([organizationId, previousSourceRef])
|
||||
@@index([organizationId, loyaltyPoints])
|
||||
@@index([companyId])
|
||||
@@index([ownerId])
|
||||
|
||||
@@ -53,6 +53,8 @@ type PersonRow = {
|
||||
previousLastName: string | null;
|
||||
importSource: string;
|
||||
sourceRef: string | null;
|
||||
previousImportSource: string | null;
|
||||
previousSourceRef: string | null;
|
||||
email: string | null;
|
||||
phone: string | null;
|
||||
mobile: string | null;
|
||||
@@ -119,6 +121,7 @@ export class PeopleService {
|
||||
{ mobile: { contains: q.trim() } },
|
||||
{ phone: { contains: q.trim() } },
|
||||
{ sourceRef: { contains: q.trim() } },
|
||||
{ previousSourceRef: { contains: q.trim() } },
|
||||
{ company: { name: { contains: q.trim(), mode: 'insensitive' } } },
|
||||
],
|
||||
}
|
||||
@@ -373,6 +376,8 @@ export class PeopleService {
|
||||
previousFullName: previousFullName || null,
|
||||
importSource,
|
||||
sourceRef: row.sourceRef,
|
||||
previousImportSource: row.previousImportSource,
|
||||
previousSourceRef: row.previousSourceRef,
|
||||
fullName: `${row.firstName} ${row.lastName}`.trim(),
|
||||
email: row.email,
|
||||
phone: row.phone,
|
||||
|
||||
@@ -29,6 +29,45 @@ export function personImportSourceLabel(source: string): string {
|
||||
return source || PERSON_IMPORT_SOURCE_LABELS.manual;
|
||||
}
|
||||
|
||||
type SourceSnapshot = {
|
||||
importSource?: string | null;
|
||||
sourceRef?: string | null;
|
||||
previousImportSource?: string | null;
|
||||
previousSourceRef?: string | null;
|
||||
customFields?: unknown;
|
||||
};
|
||||
|
||||
function didarIdFrom(customFields: unknown): string | null {
|
||||
if (!customFields || typeof customFields !== 'object') return null;
|
||||
const raw = (customFields as Record<string, unknown>).didarId;
|
||||
if (typeof raw === 'number' && Number.isFinite(raw)) return String(raw);
|
||||
if (typeof raw === 'string' && raw.trim()) return raw.trim();
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* وقتی شخصِ موجود از منبع دیگری دوباره وارد میشود، منبع و کد فعلی را بهعنوان «قبلی» نگه میدارد.
|
||||
* اگر منبع یکسان باشد (ورود مجدد از همان منبع)، مقادیر قبلی دست نمیخورند.
|
||||
*/
|
||||
export function carryPreviousSource(
|
||||
existing: SourceSnapshot,
|
||||
nextSource: PersonImportSource,
|
||||
): { previousImportSource?: string | null; previousSourceRef?: string | null } {
|
||||
const currentSource = normalizePersonImportSource(existing.importSource);
|
||||
if (currentSource === nextSource) return {};
|
||||
let ref = existing.sourceRef?.trim() || null;
|
||||
let source: string = currentSource;
|
||||
if (!ref) {
|
||||
const didarId = didarIdFrom(existing.customFields);
|
||||
if (didarId && nextSource !== 'didar') {
|
||||
ref = didarId;
|
||||
source = 'didar';
|
||||
}
|
||||
}
|
||||
if (source === 'manual' && !ref) return {};
|
||||
return { previousImportSource: source, previousSourceRef: ref };
|
||||
}
|
||||
|
||||
export function namesDiffer(
|
||||
aFirst: string,
|
||||
aLast: string,
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
} from '@nestjs/common';
|
||||
import { Prisma } from '@prisma/client';
|
||||
import { PrismaService } from '../../prisma/prisma.service';
|
||||
import { carryPreviousSource } from '../../contacts/person-source';
|
||||
import { TahesabConnectionsService } from './connections.service';
|
||||
import {
|
||||
CreateInTahesabDto,
|
||||
@@ -514,6 +515,7 @@ export class TahesabPersonLinkService {
|
||||
previousLastName: prevLast || null,
|
||||
}
|
||||
: {}),
|
||||
...carryPreviousSource(existing, 'tahesab'),
|
||||
importSource: 'tahesab',
|
||||
sourceRef,
|
||||
...(mobile
|
||||
@@ -549,6 +551,7 @@ export class TahesabPersonLinkService {
|
||||
previousLastName: byMobile.lastName || null,
|
||||
}
|
||||
: {}),
|
||||
...carryPreviousSource(byMobile, 'tahesab'),
|
||||
importSource: 'tahesab',
|
||||
sourceRef,
|
||||
},
|
||||
|
||||
@@ -655,7 +655,13 @@ onMounted(load);
|
||||
قبلی: {{ p.previousFullName }}
|
||||
</div>
|
||||
</td>
|
||||
<td>{{ sourceLabel(p.importSource || 'manual') }}</td>
|
||||
<td>
|
||||
{{ sourceLabel(p.importSource || 'manual') }}
|
||||
<div v-if="p.previousImportSource" class="muted tiny">
|
||||
قبلاً: {{ sourceLabel(p.previousImportSource) }}
|
||||
<bdi v-if="p.previousSourceRef">{{ toFaDigits(p.previousSourceRef) }}</bdi>
|
||||
</div>
|
||||
</td>
|
||||
<td dir="ltr">{{ externalCodeLabel(p) }}</td>
|
||||
<td>{{ p.position || '—' }}</td>
|
||||
<td>
|
||||
|
||||
@@ -148,18 +148,25 @@ const externalCode = computed(() => {
|
||||
}
|
||||
return '';
|
||||
});
|
||||
const SOURCE_LABELS: Record<string, string> = {
|
||||
manual: 'دستی',
|
||||
tahesab: 'تهحساب',
|
||||
didar: 'دیدار',
|
||||
excel: 'اکسل',
|
||||
phone: 'مخاطبین گوشی',
|
||||
lead_form: 'فرم لید',
|
||||
other: 'سایر',
|
||||
};
|
||||
|
||||
const importSourceLabel = computed(() => {
|
||||
const src = person.value?.importSource || 'manual';
|
||||
const map: Record<string, string> = {
|
||||
manual: 'دستی',
|
||||
tahesab: 'تهحساب',
|
||||
didar: 'دیدار',
|
||||
excel: 'اکسل',
|
||||
phone: 'مخاطبین گوشی',
|
||||
lead_form: 'فرم لید',
|
||||
other: 'سایر',
|
||||
};
|
||||
return map[src] || src;
|
||||
return SOURCE_LABELS[src] || src;
|
||||
});
|
||||
|
||||
const previousSourceLabel = computed(() => {
|
||||
const src = person.value?.previousImportSource;
|
||||
if (!src) return '';
|
||||
return SOURCE_LABELS[src] || src;
|
||||
});
|
||||
|
||||
const lastCallAt = computed(() => {
|
||||
@@ -643,6 +650,12 @@ watch(() => route.query.tab, readTab, { immediate: true });
|
||||
<span v-if="person.previousFullName" class="meta-chip">
|
||||
نام قبلی: {{ person.previousFullName }}
|
||||
</span>
|
||||
<span v-if="previousSourceLabel" class="meta-chip prev">
|
||||
قبلاً در {{ previousSourceLabel }}
|
||||
<template v-if="person.previousSourceRef">
|
||||
— کد <bdi>{{ toFaDigits(person.previousSourceRef) }}</bdi>
|
||||
</template>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hero-side">
|
||||
@@ -1185,6 +1198,11 @@ watch(() => route.query.tab, readTab, { immediate: true });
|
||||
color: #0f766e;
|
||||
}
|
||||
|
||||
.meta-chip.prev {
|
||||
background: #fef3c7;
|
||||
color: #92400e;
|
||||
}
|
||||
|
||||
.hero-side {
|
||||
display: grid;
|
||||
justify-items: end;
|
||||
|
||||
@@ -747,6 +747,9 @@ export interface PersonDto {
|
||||
previousFullName: string | null;
|
||||
importSource: PersonImportSource | string;
|
||||
sourceRef: string | null;
|
||||
/** منبع/کد قبل از آخرین ورود از منبع دیگر (مثلاً کد دیدار پیش از تهحساب) */
|
||||
previousImportSource: PersonImportSource | string | null;
|
||||
previousSourceRef: string | null;
|
||||
fullName: string;
|
||||
email: string | null;
|
||||
phone: string | null;
|
||||
|
||||
Reference in New Issue
Block a user