/** * Lead qualification fields (custom AD_User columns) — shared by the lead page, the * create/view pages and the leads list. Server counterpart: server/utils/leads/leadQualification.ts. * Values are written through PUT /api/admin/users/:id/flags (one key at a time, autosave). */ export const LEAD_QUALIFY_KEYS = [ 'leadRating', 'followUpDate', 'followUpNote', 'monthlyOrders', 'skuCount', 'shopSystem', 'expectedMonthlyValue', 'winProbability', 'expectedStartDate', 'aiScore', 'avgParcelWeight', 'leadStreet', 'leadZip', 'leadCity', 'leadCountry', 'businessModel', 'lostReason' ] as const const pick = (res: any, ...keys: string[]) => { for (const k of keys) if (res && res[k] !== undefined && res[k] !== null) return res[k] return null } const numOrEmpty = (v: any): number | '' => { const n = Number(v); return v === null || v === '' || !Number.isFinite(n) ? '' : n } /** iDempiere record (PascalCase or lowercase) → form values. Lists → { id, label } pairs. */ export const mapLeadQualification = (res: any) => ({ leadRating: Number(pick(res, 'LeadRating', 'leadRating')) || 0, followUpDate: String(pick(res, 'FollowUpDate', 'followUpDate') || '').slice(0, 10), followUpNote: String(pick(res, 'FollowUpNote', 'followUpNote') || ''), monthlyOrders: numOrEmpty(pick(res, 'MonthlyOrders', 'monthlyOrders')), skuCount: numOrEmpty(pick(res, 'SkuCount', 'skuCount')), shopSystem: String(pick(res, 'ShopSystem', 'shopSystem') || ''), expectedMonthlyValue: numOrEmpty(pick(res, 'ExpectedMonthlyValue', 'expectedMonthlyValue')), winProbability: numOrEmpty(pick(res, 'WinProbability', 'winProbability')), expectedStartDate: String(pick(res, 'ExpectedStartDate', 'expectedStartDate') || '').slice(0, 10), aiScore: numOrEmpty(pick(res, 'AiScore', 'aiScore')), avgParcelWeight: String(pick(res, 'AvgParcelWeight', 'avgParcelWeight') || ''), leadStreet: String(pick(res, 'LeadStreet', 'leadStreet') || ''), leadZip: String(pick(res, 'LeadZip', 'leadZip') || ''), leadCity: String(pick(res, 'LeadCity', 'leadCity') || ''), leadCountry: String(pick(res, 'LeadCountry', 'leadCountry') || ''), businessModel: String(pick(res, 'BusinessModel', 'businessModel')?.id ?? pick(res, 'BusinessModel', 'businessModel') ?? ''), businessModelLabel: String(pick(res, 'BusinessModel', 'businessModel')?.identifier ?? ''), lostReason: String(pick(res, 'LostReason', 'lostReason')?.id ?? pick(res, 'LostReason', 'lostReason') ?? ''), lostReasonLabel: String(pick(res, 'LostReason', 'lostReason')?.identifier ?? '') }) /** Reference lists (BusinessModel / LostReason) read from iDempiere via the API, cached. */ export const useLeadLists = () => { const { data } = useLookup('/api/admin/users/lead-lists') const businessModel = computed>(() => Array.isArray((data.value as any)?.businessModel) ? (data.value as any).businessModel : []) const lostReason = computed>(() => Array.isArray((data.value as any)?.lostReason) ? (data.value as any).lostReason : []) const labelOf = (list: Array<{ id: string; name: string }>, id: any, fallback = '') => list.find((i) => i.id === String(id))?.name || fallback return { businessModel, lostReason, labelOf } } export const localIsoToday = () => { const d = new Date(); return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}` } export const isoInDays = (days: number) => { const d = new Date(); d.setDate(d.getDate() + days); return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}` } /** Days from today to an ISO date (negative = overdue) and a state class for styling. */ export const followUpInfo = (iso: any): { diff: number | null; state: '' | 'overdue' | 'today' | 'soon' | 'later' } => { const s = String(iso || '').slice(0, 10) if (!/^\d{4}-\d{2}-\d{2}$/.test(s)) return { diff: null, state: '' } const diff = Math.round((Date.parse(s) - Date.parse(localIsoToday())) / 86400000) return { diff, state: diff < 0 ? 'overdue' : diff === 0 ? 'today' : diff <= 3 ? 'soon' : 'later' } } export const formatDateDeShort = (iso: any) => { const m = String(iso || '').match(/^(\d{4})-(\d{2})-(\d{2})/); return m ? `${m[3]}.${m[2]}.${m[1]}` : '' } export const formatEur = (v: any) => { const n = Number(v); return v === '' || v === null || !Number.isFinite(n) ? '' : n.toLocaleString('de-DE', { style: 'currency', currency: 'EUR', maximumFractionDigits: 0 }) } export const formatInt = (v: any) => { const n = Number(v); return v === '' || v === null || !Number.isFinite(n) ? '' : n.toLocaleString('de-DE') }