/** * Resolve the e-mail address of a lead / contact (AD_User) server-side, so the * mail routes never search the mailboxes for a client-supplied address. */ import { isValidEmail } from './leadMailbox' import fetchHelper from '../fetchHelper' import refreshTokenHelper from '../refreshTokenHelper' import { isAdminRole } from '../changeLogHelper' export const resolveLeadEmail = async (event: any, leadId: number, token: string): Promise<{ email: string; name: string }> => { if (!leadId) return { email: '', name: '' } const url = `models/ad_user/${leadId}?$select=Name,EMail` let u: any try { u = await fetchHelper(event, url, 'GET', token, null) } catch { const fresh = await refreshTokenHelper(event) u = await fetchHelper(event, url, 'GET', fresh, null) } const email = String(u?.EMail || u?.eMail || '').trim().toLowerCase() return { email: isValidEmail(email) ? email : '', name: String(u?.Name || u?.name || '') } } export interface MailAddress { email: string; label: string } // Partner address sets are asked for once per (mailbox x address) request of the // modal — a short cache keeps that from hitting iDempiere every time. const PARTNER_TTL_MS = 60 * 1000 const MAX_PARTNER_ADDRESSES = 10 const partnerCache = new Map() /** * Every e-mail address of a business partner: C_BPartner.EMail / EMail_Invoice plus * the EMail of its active contacts (AD_User), de-duplicated, max. 10. */ export const resolvePartnerEmails = async (event: any, partnerId: number, token: string): Promise<{ name: string; addresses: MailAddress[] }> => { if (!partnerId) return { name: '', addresses: [] } const hit = partnerCache.get(partnerId) if (hit && Date.now() - hit.at < PARTNER_TTL_MS) return { name: hit.name, addresses: hit.addresses } const get = async (url: string) => { try { return await fetchHelper(event, url, 'GET', token, null) } catch { const fresh = await refreshTokenHelper(event) return await fetchHelper(event, url, 'GET', fresh, null) } } const bp: any = await get(`models/c_bpartner/${partnerId}?$select=Name,EMail,EMail_Invoice`) const users: any = await get(`models/ad_user?$filter=${encodeURIComponent(`C_BPartner_ID eq ${partnerId} AND IsActive eq true`)}&$select=Name,EMail&$orderby=${encodeURIComponent('Name asc')}&$top=50`) const name = String(bp?.Name || bp?.name || '') const addresses: MailAddress[] = [] const add = (raw: any, label: string) => { const email = String(raw || '').trim().toLowerCase() if (!isValidEmail(email) || addresses.some((a) => a.email === email)) return if (addresses.length < MAX_PARTNER_ADDRESSES) addresses.push({ email, label }) } add(bp?.EMail ?? bp?.eMail, name) add(bp?.EMail_Invoice ?? bp?.Email_Invoice ?? bp?.eMail_Invoice, name) for (const u of (users?.records || [])) add(u?.EMail ?? u?.eMail, String(u?.Name || u?.name || '')) partnerCache.set(partnerId, { at: Date.now(), name, addresses }) return { name, addresses } } /** * Whose mail history a /api/mail/lead-emails* request is about: * - `leadId` -> the one address of that AD_User (staff, as before) * - `partnerId` -> the partner's address set, ADMIN roles only; `address` picks one * of them and is only accepted when it is in the server-resolved set. * `email` is '' when there is nothing to search (no address / unknown address). */ export const resolveMailTarget = async (event: any, q: any, token: string): Promise<{ email: string; name: string; addresses: MailAddress[]; isPartner: boolean }> => { const partnerId = Number(q?.partnerId) || 0 if (partnerId) { if (!(await isAdminRole(event, token))) throw createError({ statusCode: 403, statusMessage: 'Forbidden' }) const { name, addresses } = await resolvePartnerEmails(event, partnerId, token) const want = String(q?.address || '').trim().toLowerCase() const email = want ? (addresses.find((a) => a.email === want)?.email || '') : '' return { email, name, addresses, isPartner: true } } const { email, name } = await resolveLeadEmail(event, Number(q?.leadId) || 0, token) return { email, name, addresses: email ? [{ email, label: name }] : [], isPartner: false } }