/** * GET /api/accounting/incoming-invoices/match-vendor?vat=&iban=&name=&email= * * Reuse-first vendor matching for the review card. Order of preference: * 1. a c_bpartner already REMEMBERED on the learned vendor profile (fingerprint), * 2. exact TaxID match, 3. exact EMail match, 4. name contains. * Returns a "remembered" match (if any) + a candidate list so the UI can default * to reuse and only offer "create new" when nothing matches. */ import { string } from 'alga-js' import refreshTokenHelper from '../../../utils/refreshTokenHelper' import errorHandlingHelper from '../../../utils/errorHandlingHelper' import fetchHelper from '../../../utils/fetchHelper' import { getInboxScope, buildInboxCtx } from '../../../utils/inbox/scope' import { getVendorProfile } from '../../../utils/inbox/inboxDb' import { fingerprintFromData } from '../../../utils/inbox/vendorProfile' const slim = (r: any) => ({ id: r.id, name: r.Name, taxId: r.TaxID || r.taxID || '', isVendor: r.IsVendor ?? r.isVendor ?? false, email: r.EMail || r.eMail || '' }) const handleFunc = async (event: any, authToken: any = null) => { const token = authToken ?? await getTokenHelper(event) const organizationId = getCookie(event, 'logship_organization_id') const scope = getInboxScope(event) const q = getQuery(event) const vat = String(q.vat || '').trim() const iban = String(q.iban || '').trim() const name = String(q.name || '').trim() const email = String(q.email || '').trim() const out: any = { status: 200, remembered: null, candidates: [] } // 1. remembered profile binding const fp = fingerprintFromData({ seller: { name, vatId: vat }, paymentIban: iban } as any) if (fp) { try { const profile = await getVendorProfile(buildInboxCtx(event, token), fp) if (profile?.c_bpartner_id) { const r: any = await fetchHelper(event, `models/c_bpartner/${profile.c_bpartner_id}`, 'GET', token, null) if (r?.id) out.remembered = slim(r) } } catch {} } const seen = new Set() const add = (records: any[]) => { for (const r of records || []) { if (r?.id && !seen.has(r.id)) { seen.add(r.id); out.candidates.push(slim(r)) } } } const orgFilter = `AD_Org_ID eq ${organizationId} AND IsVendor eq true` try { if (vat) { const r: any = await fetchHelper(event, `models/c_bpartner?$filter=${string.urlEncode(`${orgFilter} AND TaxID eq '${vat}'`)}`, 'GET', token, null) add(r?.records || []) } if (email && out.candidates.length === 0) { const r: any = await fetchHelper(event, `models/c_bpartner?$filter=${string.urlEncode(`${orgFilter} AND EMail eq '${email}'`)}`, 'GET', token, null) add(r?.records || []) } if (name && out.candidates.length < 5) { const safe = name.replace(/'/g, '').slice(0, 40) const r: any = await fetchHelper(event, `models/c_bpartner?$filter=${string.urlEncode(`${orgFilter} AND contains(Name,'${safe}')`)}`, 'GET', token, null) add(r?.records || []) } } catch (e) { /* fail-soft: return whatever matched */ } return out } export default defineEventHandler(async (event) => { let data: any = {} try { data = await handleFunc(event) } catch (err: any) { try { const authToken: any = await refreshTokenHelper(event) data = await handleFunc(event, authToken) } catch (error: any) { data = errorHandlingHelper(err?.data ?? err, error?.data ?? error) } } return data })