// Adapter: maps the page-side "record + messages" shape scraped from // Alibaba's CRM pages to the backend `/api/ingest` zod PayloadSchema. // Trim undefined/empty/null to null for optional string fields. const s = v => (v == null || v === '') ? null : v; // Coerce numeric strings (e.g. "39") to number; otherwise null. const n = v => { if (v == null || v === '') return null; const x = Number(v); return Number.isFinite(x) ? x : null; }; // 只接受标准 ISO(YYYY-MM-DDTHH:...);非 ISO(列表页 "11:58"/"2026-7-8")跳过 → 兜底当前时间 // 注:取消聊天时无 gmtCreate,列表时间为非 ISO,这里保证 createTime/updateTime 始终合法 ISO。 // 准确时间待抓聊天时由 gmtCreate 通过 upsert 覆盖。 const isIso = v => typeof v === 'string' && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/.test(v); // 解析 alibaba 列表的非 ISO 时间: // "2026-7-1"(YYYY-M-D,创建时间)→ 当天 00:00(本地) // "13:58"(HH:mm,今天的更新时间)→ 今天该时分(本地) const parseListTime = v => { if (v == null || v === '') return null; const s = String(v).trim(); const d = s.match(/^(\d{4})-(\d{1,2})-(\d{1,2})$/); if (d) return new Date(Number(d[1]), Number(d[2]) - 1, Number(d[3])).toISOString(); const hm = s.match(/^(\d{1,2}):(\d{2})$/); if (hm) { const n = new Date(); return new Date(n.getFullYear(), n.getMonth(), n.getDate(), Number(hm[1]), Number(hm[2])).toISOString(); } return null; }; const isoFallback = (...vals) => { for (const v of vals) { if (isIso(v)) return v; const p = parseListTime(v); if (p) return p; } return new Date(Date.now()).toISOString(); }; export function buildIngestPayload(record, messages) { // 阶段1:允许 messages 为空(先走通列表+客户+行为,聊天后续补充) const msgs = messages || []; const iq = record || {}; const inquiry = { inquiryId: String(iq.inquiryId), manager: s(iq.manager) || undefined, createTime: isoFallback(iq.createTimeFromApi, iq.createTime), updateTime: isoFallback( iq.updateTime, iq.updateTimeFromApi, iq.createTimeFromApi, ), status: s(iq.status) || undefined, msgPreview: s(iq.msgPreview) || undefined, isFlagged: !!iq.isFlagged, }; const customer = iq.customerId ? { customerId: String(iq.customerId), name: s(iq.detailBuyerName) || s(iq.buyerName), country: s(iq.detailCountry) || s(iq.countryCode), email: s(iq.email), phone: s(iq.phone), landline: s(iq.landline), businessType: s(iq.businessType), salesPlatform: s(iq.salesPlatform), website: s(iq.website), company: s(iq.company), registerTime: s(iq.registerTime), avatarUrl: s(iq.detailAvatarUrl) || s(iq.avatarUrl) || undefined, buyerTags: s(iq.buyerTags) || undefined, buyerFeatures: s(iq.buyerFeatures) || undefined, recentInquiryProducts: Array.isArray(iq.recentInquiryProducts) ? iq.recentInquiryProducts : undefined, storeInquiryProduct: s(iq.storeInquiryProduct) || undefined, storeInquiryQty: s(iq.storeInquiryQty) || undefined, storeVisitProducts: Array.isArray(iq.storeVisitProducts) ? iq.storeVisitProducts : undefined, behavior: iq.behavior ? { productViews: n(iq.behavior.productViews), validInquiries: n(iq.behavior.validInquiries), validRfq: n(iq.behavior.validRFQ), // detail 用 validRFQ(大写) loginDays: n(iq.behavior.loginDays), spamInquiries: n(iq.behavior.spamInquiries), blacklistCount: n(iq.behavior.blacklistCount), topIndustry: s(iq.behavior.topIndustry), } : undefined, } : null; const out = { inquiry, customer, messages: msgs.map(m => ({ messageId: String(m.messageId), sendTime: m.sendTime, sender: s(m.sender) || undefined, direction: m.direction, content: m.original ?? m.content ?? '', translation: s(m.translation) ?? '', })), }; return out; }