import fs from 'node:fs'; import path from 'node:path'; import process from 'node:process'; import dotenv from 'dotenv'; const rootDir = path.resolve(process.cwd()); dotenv.config({ path: path.join(rootDir, '.env') }); const toBool = (value, fallback) => { if (value == null) return fallback; return ['1', 'true', 'yes', 'y', 'on'].includes(String(value).trim().toLowerCase()); }; const ensureDir = (dirPath) => { fs.mkdirSync(dirPath, { recursive: true }); return dirPath; }; export const config = { rootDir, baseUrl: process.env.ALIYUN_APS_BASE_URL || 'https://aps.aliyun.com', headless: toBool(process.env.ALIYUN_APS_HEADLESS, false), browserMode: (process.env.ALIYUN_APS_BROWSER_MODE || 'launch').trim().toLowerCase(), browserChannel: (process.env.ALIYUN_APS_BROWSER_CHANNEL || '').trim(), browserExecutablePath: (process.env.ALIYUN_APS_BROWSER_EXECUTABLE_PATH || '').trim(), cdpUrl: (process.env.ALIYUN_APS_CDP_URL || 'http://127.0.0.1:9222').trim(), timezone: process.env.ALIYUN_APS_TIMEZONE || 'Asia/Shanghai', cron: process.env.ALIYUN_APS_CRON || '0 6 * * *', orderStartDate: process.env.ALIYUN_APS_ORDER_START_DATE || '2024-01-01', incrementalOrderStartDate: process.env.ALIYUN_APS_INCREMENTAL_ORDER_START_DATE || '', billStartMonth: process.env.ALIYUN_APS_BILL_START_MONTH || '2024-01', orderIncrementalOverlapDays: Math.max(0, Number.parseInt(process.env.ALIYUN_APS_ORDER_INCREMENTAL_OVERLAP_DAYS || '2', 10) || 2), billIncrementalOverlapDays: Math.max(0, Number.parseInt(process.env.ALIYUN_APS_BILL_INCREMENTAL_OVERLAP_DAYS || '7', 10) || 7), messageIncrementalOverlapDays: Math.max(0, Number.parseInt(process.env.ALIYUN_APS_MESSAGE_INCREMENTAL_OVERLAP_DAYS || '7', 10) || 7), scheduleMode: process.env.ALIYUN_APS_SCHEDULE_MODE || 'incremental', smtp: { host: process.env.ALIYUN_APS_SMTP_HOST || 'smtp.qq.com', port: parseInt(process.env.ALIYUN_APS_SMTP_PORT || '465', 10), secure: toBool(process.env.ALIYUN_APS_SMTP_SECURE, true), user: process.env.ALIYUN_APS_SMTP_USER || '', pass: process.env.ALIYUN_APS_SMTP_PASS || '', }, notifyEmail: process.env.ALIYUN_APS_NOTIFY_EMAIL || '', closeBrowser: toBool(process.env.ALIYUN_APS_CLOSE_BROWSER, true), fullSync: toBool(process.env.ALIYUN_APS_FULL_SYNC, true), resumeBillMonth: process.env.ALIYUN_APS_RESUME_BILL_MONTH || '', resumeBillPage: Math.max(1, Number.parseInt(process.env.ALIYUN_APS_RESUME_BILL_PAGE || '1', 10) || 1), userDataDir: ensureDir(path.join(rootDir, '.browser')), storageStateFile: path.join(rootDir, '.browser', 'storage-state.json'), dataDir: ensureDir(path.join(rootDir, 'data')), downloadDir: ensureDir(path.join(rootDir, 'downloads')), errorDir: ensureDir(path.join(rootDir, 'data', 'errors')), db: { host: process.env.ALIYUN_APS_DB_HOST || '', port: parseInt(process.env.ALIYUN_APS_DB_PORT || '3306', 10), user: process.env.ALIYUN_APS_DB_USER || '', password: process.env.ALIYUN_APS_DB_PASSWORD || '', database: process.env.ALIYUN_APS_DB_NAME || '', charset: process.env.ALIYUN_APS_DB_CHARSET || 'utf8mb4', connectionLimit: Math.max(1, Number.parseInt(process.env.ALIYUN_APS_DB_CONNECTION_LIMIT || '5', 10) || 5), connectTimeout: Math.max(1000, Number.parseInt(process.env.ALIYUN_APS_DB_CONNECT_TIMEOUT || '20000', 10) || 20000), }, }; export const datasets = { customers: { name: 'customers', url: `${config.baseUrl}/#/detail/my_customer/~/customer/list`, heading: '我的客户', pageSize: 20, uniqueKey: (record) => record.accountId || record.loginName || record.__hash, normalize: (record) => { const loginAndUid = record['登录名称/账号ID'] || ''; const [loginName = '', accountId = ''] = splitLines(loginAndUid); return { loginName: loginName.replace(/\s+/g, ''), accountId, listPageNum: context.pageNum || '', realName: record['UID实名认证名称'] || '', reportSource: record['报备来源'] || '', reportType: record['报备类型'] || '', tradeMode: record['交易模式'] || '', authStatus: record['实名认证状态'] || '', relationTime: record['关联日期'] || '', owner: record['跟进员工'] || '', cashBalanceCny: record['账户现金余额(CNY)'] || '', invoicePendingCny: record['待开票金额(CNY)'] || '', lastMonthConsumptionCny: record['上月消费金额(CNY)'] || '', thisMonthConsumptionCny: record['本月消费金额(CNY)'] || '', paymentNoticeStatus: record['代为支付告知状态'] || '', inviteType: record['邀约注册类型'] || '', isNewCustomer: record['是否新客户'] || '', isPerformanceQualified: record['是否达成业绩起算点'] || '', customerCategory: record['客户分类'] || '', remark: record['备注'] || '', inactiveMonths: record['客户无消费月数'] || '', releasePlanTime: record['计划释放时间'] || '', releasePlanReason: record['计划释放原因'] || '', }; }, }, orders: { name: 'orders', url: `${config.baseUrl}/#/detail/order/~/costCenter/order`, heading: '订单查询', pageSize: 20, uniqueKey: (record) => record.orderId || record.__hash, normalize: (record, context) => ({ orderId: record['订单号'] || '', customerAccount: (record['客户账号'] || '').replace(/\s+/g, ''), customerCategory: record['客户分类'] || '', orderType: record['订单类型'] || '', orderOriginalPriceCny: record['订单原价 (CNY)'] || '', actualPaidCny: record['实付金额 (CNY)'] || '', orderStatus: record['订单状态'] || '', createdAt: record['下单时间'] || '', windowStart: context.windowStart || '', windowEnd: context.windowEnd || '', }), }, orderDetails: { name: 'orderDetails', url: `${config.baseUrl}/#/detail/order/~/costCenter/order`, heading: '订单详情', pageSize: 100, uniqueKey: (record) => record.orderId || record.__hash, normalize: (record, context) => ({ orderId: record.orderId || '', orderType: record.orderType || '', status: record.status || '', tradeType: record.tradeType || '', customerCategory: record.customerCategory || '', dealerName: record.dealerName || '', dealerUid: record.dealerUid || '', customerType: record.customerType || '', opportunityId: record.opportunityId || '', paymentTime: record.paymentTime || '', orderTime: record.orderTime || '', productName: record.productName || '', productCode: record.productCode || '', originalPriceCny: record.originalPriceCny || '', paidAmountCny: record.paidAmountCny || '', discount: record.discount || '', payableAmountCny: record.payableAmountCny || '', couponAmountCny: record.couponAmountCny || '', windowStart: context.windowStart || '', windowEnd: context.windowEnd || '', }), }, customerDetails: { name: 'customerDetails', url: `${config.baseUrl}/#/detail/my_customer/~/customer/list`, heading: '详情', pageSize: 100, uniqueKey: (record) => record.accountId || record.__hash, normalize: (record, context) => ({ accountId: context.accountId || '', customerAccount: record.customerAccount || '', customerName: record.customerName || '', customerType: record.customerType || '', tradeMode: record.tradeMode || '', customerSource: record.customerSource || '', realNameStatus: record.realNameStatus || '', email: record.email || '', relationDate: record.relationDate || '', phone: record.phone || '', remark: record.remark || '', paymentNoticeStatus: record.paymentNoticeStatus || '', department: record.department || '', lastMonthPayableTotalCny: record.lastMonthPayableTotalCny || '', lastMonthPrepayCny: record.lastMonthPrepayCny || '', lastMonthPostpayCny: record.lastMonthPostpayCny || '', currentMonthPayableTotalCny: record.currentMonthPayableTotalCny || '', currentMonthPrepayCny: record.currentMonthPrepayCny || '', currentMonthPostpayCny: record.currentMonthPostpayCny || '', }), }, bills: { name: 'bills', url: `${config.baseUrl}/#/detail/bill/~/costCenter/bill`, heading: '账单查询', pageSize: 20, uniqueKey: (record) => record.__hash, normalize: (record, context) => ({ billingMonth: record['账期'] || '', consumeDate: record['消费时间'] || '', customerAccount: (record['客户账号'] || '').replace(/\s+/g, ''), customerCategory: record['客户分类'] || '', productCategory: record['产品分类'] || '', productName: record['产品名称'] || '', originalPriceCny: record['原价 (CNY)'] || '', customerPayableCny: record['客户应付金额 (CNY)'] || '', billType: record['账单类型'] || '', countsForPerformance: record['是否计入业绩'] || '', commissionable: record['是否返佣'] || '', commissionMonth: record['佣金月份'] || context.month || '', inviteType: record['邀约注册类型'] || '', serviceStartAt: record['服务开始时间'] || '', serviceEndAt: record['服务结束时间'] || '', }), }, messages: { name: 'messages', url: `${config.baseUrl}/#/message`, heading: '消息', pageSize: 20, uniqueKey: (record) => record.msgId || record.__hash, normalize: (record) => ({ msgId: pickFirst(record, ['消息ID', 'msg_id', '消息id', 'ID', 'id']), title: pickFirst(record, ['消息标题', '标题', 'title']), content: pickFirst(record, ['消息内容', '内容', 'content']), msgType: pickFirst(record, ['消息类型', 'type', 'msg_type']), fromApp: pickFirst(record, ['来源应用', 'from_app', '应用']), bizCode: pickFirst(record, ['业务编码', 'biz_code']), msgChannel: pickFirst(record, ['消息通道', 'msg_channel']), categoryId: pickFirst(record, ['分类ID', 'category_id']), categoryName: pickFirst(record, ['分类名称', 'category_name']), lv1CategoryId: pickFirst(record, ['一级分类ID', 'lv1_category_id']), lv2CategoryId: pickFirst(record, ['二级分类ID', 'lv2_category_id']), lv3CategoryId: pickFirst(record, ['三级分类ID', 'lv3_category_id']), messageClassification: pickFirst(record, ['归类结果', 'message_classification']), customerName: pickFirst(record, ['客户名称', 'customer_name']), orderNo: pickFirst(record, ['订单号', 'order_no']), status: pickFirst(record, ['消息状态', '状态', 'status']), gmtCreated: pickFirst(record, ['消息创建时间', '创建时间', 'gmt_created']), gmtModified: pickFirst(record, ['消息修改时间', '修改时间', 'gmt_modified']), extraData: record, }), }, }; function splitLines(value) { return String(value) .split('\n') .map((part) => part.trim()) .filter(Boolean); } function pickFirst(record, keys) { for (const key of keys) { const value = record[key]; if (value != null && String(value).trim()) { return String(value).trim(); } } return ''; }