98 lines
3.7 KiB
TypeScript
98 lines
3.7 KiB
TypeScript
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 };
|
|
}
|