Files
CRM_Montzar/apps/web/src/lib/person-insights.ts
T

98 lines
3.7 KiB
TypeScript
Raw Normal View History

import { brand, brandSuggestionFa, type MemoryItemDto, type NoteDto, type PersonDto } from '@mento/shared';
export type ProbabilityBand = 'بالا' | 'متوسط' | 'پایین';
export function personInitials(fullName: string | null | undefined): string {
const parts = fullName?.trim().split(/\s+/) ?? [];
if (!parts.length) return 'م';
return parts
.slice(0, 2)
.map((p) => p[0])
.join('');
}
export function isVipPerson(person: PersonDto | null): boolean {
if (!person) return false;
const tier = person.loyaltyTier?.name ?? '';
if (/vip|طلا|طلایی|gold|پلاتین|platinum/i.test(tier)) return true;
if ((person.loyaltyPoints ?? 0) >= 800) return true;
const fields = person.customFields ?? {};
for (const [key, value] of Object.entries(fields)) {
if (/vip/i.test(key) && (value === true || value === 'true' || value === 1 || value === '1')) {
return true;
}
if (typeof value === 'string' && /^vip$/i.test(value.trim())) return true;
}
return false;
}
export function purchaseProbability(input: {
daysSinceContact: number | null;
openDealCount: number;
quoteCount: number;
hasUpcoming: boolean;
noteCount: number;
}): { score: number; band: ProbabilityBand } {
let score = 38;
if (input.openDealCount > 0) score += 16 + Math.min(8, input.openDealCount * 2);
if (input.quoteCount > 0) score += 8;
if (input.noteCount > 0) score += 4;
if (input.hasUpcoming) score += 6;
if (input.daysSinceContact != null) {
if (input.daysSinceContact <= 2) score += 24;
else if (input.daysSinceContact <= 7) score += 18;
else if (input.daysSinceContact <= 14) score += 8;
else if (input.daysSinceContact <= 30) score += 2;
else score -= 10;
} else {
score -= 6;
}
score = Math.max(8, Math.min(95, Math.round(score)));
const band: ProbabilityBand = score >= 70 ? 'بالا' : score >= 45 ? 'متوسط' : 'پایین';
return { score, band };
}
export function buildSmartSummary(input: {
memories: MemoryItemDto[];
notes: NoteDto[];
lastQuoteLabel: string | null;
daysSinceCall: number | null;
probabilityBand: ProbabilityBand;
}): { text: string; cta: string; ctaKind: 'call' | 'activity' } {
const parts: string[] = [];
const memoryBits = input.memories
.map((m) => m.content.trim())
.filter(Boolean)
.slice(0, 2);
if (memoryBits.length) {
parts.push(memoryBits.join(' '));
} else {
const note = input.notes.find((n) => n.body.trim());
if (note?.body) {
const snippet = note.body.replace(/\s+/g, ' ').trim();
parts.push(snippet.length > 90 ? `${snippet.slice(0, 90)}…` : snippet);
}
}
if (input.lastQuoteLabel) {
parts.push(`آخرین پیشنهاد ${input.lastQuoteLabel} بوده.`);
}
if (input.daysSinceCall != null) {
if (input.daysSinceCall <= 0) parts.push('امروز با مشتری تماس گرفته شده.');
else parts.push(`${input.daysSinceCall} روز از آخرین تماس گذشته`);
}
parts.push(`و احتمال خرید ${input.probabilityBand} است.`);
const hasSignal = memoryBits.length > 0 || input.notes.length > 0 || input.lastQuoteLabel;
const text = hasSignal
? parts.join(' ').replace(/\s+/g, ' ').trim()
: `هنوز خلاصهٔ پایداری از این مشتری ثبت نشده. با یادداشت یا حافظهٔ ${brand.nameFa}، پیشنهاد هوشمند اینجا ظاهر می‌شود.`;
let cta = brandSuggestionFa('امروز با مشتری تماس بگیر');
let ctaKind: 'call' | 'activity' = 'call';
if (input.daysSinceCall != null && input.daysSinceCall <= 1) {
cta = brandSuggestionFa('پیگیری بعدی را ثبت کن');
ctaKind = 'activity';
}
return { text, cta, ctaKind };
}