110 lines
4.0 KiB
JavaScript
110 lines
4.0 KiB
JavaScript
|
|
// 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;
|
||
|
|
}
|