(function () { 'use strict'; const state = { accounts: [], settlementPage: 1, settlementPageSize: 50, settlementPages: 1, summary: { settlementId: 0, page: 1, pageSize: 100, pages: 1 }, detail: { id: 0, number: '', account: '', page: 1, pageSize: 100 }, kingdeeFields: [], walmartFieldRows: [] }; const escHtml = value => typeof esc === 'function' ? esc(value == null ? '' : value) : String(value == null ? '' : value); const moneyText = value => Number(value || 0).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); const statusText = value => ({ pending: '待推送', success: '推送成功', failed: '推送失败', error: '推送失败' })[value] || value || '待推送'; const statusClass = value => value === 'success' ? 'success' : (value === 'failed' || value === 'error') ? 'failed' : 'pending'; function help(text) { return ''; } function installStyle() { const style = document.createElement('style'); style.textContent = ` .wm-toolbar{display:flex;justify-content:flex-end;gap:8px;align-items:center;flex-wrap:wrap} #navWalmartSettlement .wm-nav-icon{display:inline-flex;width:18px;justify-content:center;margin-right:5px;font-size:14px;vertical-align:-1px} .wm-message{display:block;max-width:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;cursor:copy} .wm-actions{display:flex;gap:6px;white-space:nowrap} .wm-actions button{padding:6px 10px} .wm-map-table input,.wm-map-table select{min-width:120px;padding:7px 9px} .wm-map-table input[type=checkbox]{min-width:0;width:16px} #walmart-settlement-details .table-wrap{max-height:calc(100vh - 330px)} .wm-summary-shell{max-width:780px;margin:0 auto;padding:2px 18px 24px;background:#fff} .wm-summary-meta{font-size:13px;color:#64748b;margin-top:4px} .wm-summary-section{padding:20px 6px 22px;border-bottom:1px solid #e5e7eb} .wm-summary-section:last-child{border-bottom:0} .wm-summary-section h3{margin:0 0 10px;color:#111827;font-size:15px;font-weight:700} .wm-summary-line,.wm-summary-total{display:grid;grid-template-columns:minmax(0,1fr) 150px;gap:18px;align-items:center;min-height:32px;color:#111827} .wm-summary-line button{appearance:none;border:0;background:transparent;padding:0;text-align:left;color:#111827;font-weight:400;cursor:pointer} .wm-summary-line button:hover{color:#1d4ed8;text-decoration:underline} .wm-summary-amount{text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap} .wm-summary-total{margin-top:4px;padding-top:7px;border-top:1px solid #f1f5f9;font-weight:700} .wm-summary-total.negative .wm-summary-amount{color:#dc2626} .wm-summary-action{display:flex;justify-content:flex-end;margin-top:9px} .wm-view-details{background:#fff!important;color:#111827!important;border:1px solid #111827!important;border-radius:18px!important;padding:6px 16px!important;font-size:12px!important} .wm-view-details:hover{background:#f8fafc!important;color:#1d4ed8!important;border-color:#1d4ed8!important} .wm-detail-toolbar{display:grid;grid-template-columns:minmax(240px,1fr) 170px 170px auto;gap:10px;align-items:end;padding:12px 14px;background:#fff;border:1px solid #dbe3ef} .wm-detail-toolbar label{color:#475569} .wm-detail-status{display:inline-flex;padding:3px 10px;border-radius:3px;background:#fff7e6;color:#9a6700;font-size:12px} .wm-mapping-status{display:inline-flex;margin-left:8px;padding:2px 7px;border-radius:3px;font-size:11px;vertical-align:1px} .wm-mapping-status.ok{background:#ecfdf3;color:#087443} .wm-mapping-status.missing{background:#fff1f2;color:#be123c} @media(max-width:900px){.wm-summary-shell{padding:0 10px}.wm-detail-toolbar{grid-template-columns:1fr 1fr}.wm-summary-line,.wm-summary-total{grid-template-columns:minmax(0,1fr) 110px}} `; document.head.appendChild(style); } function installNavigation() { if (document.getElementById('navWalmartSettlement')) return; const anchor = document.getElementById('navFit') || document.getElementById('navSystem'); const parent = anchor ? anchor.parentNode : document.querySelector('.side'); const group = document.createElement('div'); group.className = 'navgroup'; group.id = 'navWalmartSettlement'; group.innerHTML = '沃尔玛平台▸'; group.onclick = window.toggleWalmartSettlementNav; const sub = document.createElement('div'); sub.className = 'navsub'; sub.id = 'navWalmartSettlementSub'; [ ['walmart-api-config', 'API接口配置'], ['walmart-settlements', '结算单'], ['walmart-settlement-summary', '结算单汇总'], ['walmart-settlement-details', '结算明细'], ['walmart-fee-mapping', '费用项映射'], ['walmart-field-mapping', '金蝶单据字段映射'], ['walmart-schedule', '定时拉取'] ].forEach(([id, label]) => { const item = document.createElement('div'); item.className = 'nav navchild'; item.dataset.s = id; item.textContent = label; item.onclick = () => openWalmartPage(id); sub.appendChild(item); }); parent.insertBefore(group, anchor || null); parent.insertBefore(sub, anchor || null); } window.toggleWalmartSettlementNav = function () { document.getElementById('navWalmartSettlement')?.classList.toggle('open'); document.getElementById('navWalmartSettlementSub')?.classList.toggle('open'); }; function installPages() { const main = document.querySelector('main'); if (!main || document.getElementById('walmart-api-config')) return; main.insertAdjacentHTML('beforeend', `
${help('维护 Walmart Marketplace OAuth 账号。金蝶登录信息复用结算单 API 配置;FormId 和分录 Key 按沃尔玛账号独立保存。')}

沃尔玛 API接口配置

保存账号后点击“检查报告日期”,确认 Walmart 官方当前是否已生成可下载的结算报告。
账号编码账号名称市场Client IDClient SecretFormId状态操作
${help('从沃尔玛官方可用结算报告日期中发现新报告并下载。普通同步自动跳过已存在报告;覆盖同步不会覆盖已关联金蝶单据的记录。')}

沃尔玛结算单

账号结算单编号结算日期金额币种明细数推送状态金蝶单号推送信息操作
每页行
${help('结算单汇总:先选择一张结算单,再点击业务类型或费用项目查看对应明细。业务类型、原因和费用明细保留沃尔玛原始英文。')}

结算单汇总

请先从结算单列表选择一张结算单。
请先从结算单列表选择一张结算单。
${help('结算明细:可按明细 ID、交易主键、订单号、PO、SKU、交易类型或金额类型查询。业务类型、原因和费用明细保留英文。')}

结算明细

服务费用明细扣费日期金额状态
${help('费用项映射由全部沃尔玛账号共享。普通类型按汇总大类 + 汇总项目映射;Services fees 按查看明细中的 Fee Detail 映射。推送金蝶前必须完成费用编码和金额字段配置,旧的原始类型映射会保留并兼容。')}

沃尔玛费用项映射

汇总大类汇总项目 / Fee Detail映射层级金蝶费用项编码费用项字段金蝶金额字段启用
${help('金蝶字段映射按沃尔玛账号保存,可从一个沃尔玛账号复制到另一个账号。复制只覆盖目标账号,不改变源账号或独立站映射。')}

沃尔玛金蝶单据字段映射

金蝶字段名称范围取值方式来源字段/固定值基础资料分录 Key父分录 Key启用
请选择账号并读取金蝶字段
${help('定时任务只发现和下载尚未入库的结算报告,不重复下载已有报告。建议保持至少两小时一次。')}

沃尔玛定时拉取

`); } function loadWalmartPageData(id) { const loaders = { 'walmart-api-config': loadWalmartAccounts, 'walmart-settlements': () => loadWalmartSettlements(false), 'walmart-settlement-summary': () => loadWalmartSummary(false), 'walmart-settlement-details': () => state.detail.id ? loadWalmartDetails(false) : null, 'walmart-fee-mapping': loadWalmartFeeMappings, 'walmart-field-mapping': loadWalmartFieldMappings, 'walmart-schedule': loadWalmartSchedule }; if (loaders[id]) loaders[id](); } window.loadWalmartPageData = loadWalmartPageData; function openWalmartPage(id) { if (typeof showSection === 'function') showSection(id); if (typeof window.activateWorkspacePage !== 'function') loadWalmartPageData(id); } window.openWalmartPage = openWalmartPage; async function loadWalmartAccounts() { try { const data = await apiGet('/api/walmart-settlement/accounts'); state.accounts = data.rows || []; const tb = document.getElementById('wmAccountTb'); if (tb) tb.innerHTML = state.accounts.map(item => `${escHtml(item.code)}${escHtml(item.name)}${escHtml(item.marketplace)}${escHtml(item.client_id)}${escHtml(item.client_secret)}${escHtml(item.form_id)}${item.enabled ? '启用' : '停用'}`).join('') || '
暂无沃尔玛账号
'; refreshAccountSelects(); if (document.getElementById('walmart-api-config')?.classList.contains('active') && state.accounts.length && !wmAccountCode.value.trim()) editWalmartAccount(state.accounts[0].code); return data; } catch (error) { toast('加载沃尔玛账号失败:' + error.message); } } window.loadWalmartAccounts = loadWalmartAccounts; function refreshAccountSelects() { ['wmSettlementAccount', 'wmSummaryAccount', 'wmFieldAccount', 'wmFieldCopySource'].forEach(id => { const select = document.getElementById(id); if (!select) return; const current = select.value; const all = (id === 'wmSettlementAccount' || id === 'wmSummaryAccount') ? '' : ''; select.innerHTML = all + state.accounts.map(item => ``).join(''); if (Array.from(select.options).some(option => option.value === current)) select.value = current; }); } window.resetWalmartAccountForm = function () { ['wmAccountCode','wmAccountName','wmClientId','wmClientSecret','wmFormId','wmChannelType'].forEach(id => document.getElementById(id).value = ''); wmMarketplace.value = 'US'; wmAccountEnabled.value = '1'; wmEntryKey.value = 'FEntity'; wmServiceName.value = 'Walmart Marketplace'; wmAccountCode.readOnly = false; }; window.editWalmartAccount = function (code) { const item = state.accounts.find(row => row.code === code); if (!item) return; wmAccountCode.value = item.code; wmAccountCode.readOnly = true; wmAccountName.value = item.name; wmClientId.value = item.client_id; wmClientSecret.value = item.client_secret; wmMarketplace.value = item.marketplace || 'US'; wmAccountEnabled.value = String(item.enabled); wmFormId.value = item.form_id || ''; wmEntryKey.value = item.entry_key || 'FEntity'; wmServiceName.value = item.service_name || 'Walmart Marketplace'; wmChannelType.value = item.channel_type || ''; }; function accountPayload() { return { code: wmAccountCode.value.trim(), name: wmAccountName.value.trim(), client_id: wmClientId.value.trim(), client_secret: wmClientSecret.value.trim(), marketplace: wmMarketplace.value, enabled: Number(wmAccountEnabled.value), form_id: wmFormId.value.trim(), entry_key: wmEntryKey.value.trim(), service_name: wmServiceName.value.trim(), channel_type: wmChannelType.value.trim() }; } window.saveWalmartAccount = async function () { try { await apiPost('/api/walmart-settlement/accounts', accountPayload()); toast('沃尔玛账号已保存'); await loadWalmartAccounts(); } catch (error) { toast('保存失败:' + error.message); } }; window.testWalmartAccount = async function () { const code = wmAccountCode.value.trim(); if (!code) return toast('请先填写并保存账号编码'); try { showLoading('正在连接沃尔玛...'); const result = await apiPost('/api/walmart-settlement/accounts/' + encodeURIComponent(code) + '/test'); const dates=(result.available_dates||[]).join('、'); toast(result.message + (dates ? ':' + dates : ';当前没有可用报告日期')); } catch (error) { toast('连接失败:' + error.message); } finally { hideLoading(); } }; window.checkWalmartAvailableReports = async function () { const code = wmAccountCode.value.trim(); if (!code) return toast('请先填写并保存账号编码'); try { showLoading('正在查询官方报告日期...'); const result = await apiGet('/api/walmart-settlement/accounts/' + encodeURIComponent(code) + '/available-reports'); const dates=result.dates||[]; wmAvailableReports.textContent = dates.length ? 'Recon 可下载报告日期(共 ' + dates.length + ' 个):' + dates.join('、') : result.statement_date && result.payment_status==='paid' ? 'Payment Statement 已到付款确认时间,结算日期:' + result.statement_date + '。点击“立即同步”即可入库。' : result.statement_date ? '当前周期结算日期:' + result.statement_date + ',尚未确认付款;系统将在 ' + result.paid_after + ' 后允许拉取。' : '连接成功,但 Recon 和 Payment Statement 都没有返回结算日期。'; toast(result.message); } catch (error) { wmAvailableReports.textContent = '查询失败:' + error.message; toast('查询失败:' + error.message); } finally { hideLoading(); } }; window.deleteWalmartAccount = async function (code) { if (!confirm('确认删除沃尔玛账号 ' + code + ' 的 API 配置?\n删除后不可恢复;如果账号已有结算数据,系统会阻止删除。')) return; if (!confirm('请再次确认:确实要删除该客户的沃尔玛 API 配置?')) return; try { await apiDelete('/api/walmart-settlement/accounts/' + encodeURIComponent(code)); toast('API 配置已删除'); resetWalmartAccountForm(); loadWalmartAccounts(); } catch (error) { toast('删除失败:' + error.message); } }; function settlementQuery() { const query = new URLSearchParams({ page: state.settlementPage, page_size: state.settlementPageSize }); [['account_code','wmSettlementAccount'],['date_min','wmSettlementDateMin'],['date_max','wmSettlementDateMax'],['settlement_number','wmSettlementNumber'],['push_status','wmSettlementPushStatus']].forEach(([key,id]) => { const value = document.getElementById(id)?.value.trim(); if (value) query.set(key, value); }); return query; } const walmartSettlementSelectAll = () => document.getElementById('wmSettlementSelectAll'); window.loadWalmartSettlements = async function (reset) { if (reset) state.settlementPage = 1; if (!state.accounts.length) await loadWalmartAccounts(); try { const data = await apiGet('/api/walmart-settlement/settlements?' + settlementQuery()); state.settlementPages = Math.max(1, Math.ceil(data.total / data.page_size)); wmSettlementTb.innerHTML = (data.rows || []).map(row => `${escHtml(row.account_name || row.account_code)}${escHtml(row.settlement_number)}${escHtml(row.settlement_date || row.report_date)}${moneyText(row.total_amount)}${escHtml(row.currency)}${row.detail_count}${statusText(row.push_status)}${escHtml(row.kingdee_bill_no || '')}${escHtml(row.push_message || '')}${row.push_status === 'success' ? `` : ``}`).join('') || '
暂无结算单,请点击“立即同步”
'; const selectAll = walmartSettlementSelectAll(); if (selectAll) selectAll.checked = false; if (selectAll) selectAll.indeterminate = false; wmSettlementPageInfo.textContent = `第 ${data.page} / ${state.settlementPages} 页,共 ${data.total} 条`; } catch (error) { toast('加载结算单失败:' + error.message); } }; window.changeWalmartSettlementPage = function (delta) { state.settlementPage = Math.min(state.settlementPages, Math.max(1, state.settlementPage + delta)); loadWalmartSettlements(false); }; window.changeWalmartSettlementPageSize = function () { state.settlementPageSize = Number(wmSettlementPageSize.value); state.settlementPage = 1; loadWalmartSettlements(false); }; window.toggleWalmartSettlementAll = function (checked) { document.querySelectorAll('[data-wm-settlement-id]').forEach(item => { item.checked = checked; }); const selectAll = walmartSettlementSelectAll(); if (selectAll) selectAll.indeterminate = false; }; window.syncWalmartSettlementSelectAll = function () { const items = Array.from(document.querySelectorAll('[data-wm-settlement-id]')); const selected = items.filter(item => item.checked).length; const selectAll = walmartSettlementSelectAll(); if (selectAll) { selectAll.checked = items.length > 0 && selected === items.length; selectAll.indeterminate = selected > 0 && selected < items.length; } }; function includeWalmartSyncedDates(result) { const dates = (result.results || []).flatMap(account => account.results || []).filter(item => item.status==='saved' || item.status==='skipped').map(item => item.report_date).filter(Boolean).sort(); if (!dates.length) return; const first = dates[0], last = dates[dates.length - 1]; if (!wmSettlementDateMin.value || wmSettlementDateMin.value > first) wmSettlementDateMin.value = first; if (!wmSettlementDateMax.value || wmSettlementDateMax.value < last) wmSettlementDateMax.value = last; state.settlementPage = 1; } window.syncWalmartSettlements = async function (replace) { const account = wmSettlementAccount.value; const dateMin = wmSettlementDateMin.value || null; const dateMax = wmSettlementDateMax.value || null; if (replace && !account) return toast('覆盖同步必须先选择一个沃尔玛账号'); const reportDate = dateMin && dateMin === dateMax ? dateMin : ''; if (replace && !reportDate) return toast('覆盖同步必须把开始和结束日期设为同一天'); if (replace && !confirm('确认覆盖该账号指定日期的沃尔玛结算报告?已关联金蝶单据的记录不会被覆盖。')) return; try { showLoading('正在读取沃尔玛结算报告...'); const result = await apiPost('/api/walmart-settlement/settlements/sync', { account_codes: account ? [account] : null, report_date: reportDate || null, date_min: dateMin, date_max: dateMax, replace: !!replace }); if (result.duplicate) { toast(result.message); return; } includeWalmartSyncedDates(result); const failures = (result.failed || []).map(item => (item.account_code || '账号') + ':' + (item.message || '未知错误')); const usedStatement = (result.results || []).some(item => item.source === 'payment_statement'); let message; if (failures.length) message = '同步失败:' + failures.join(';'); else if (!result.accounts) message = '没有已启用的沃尔玛账号,请先到 API接口配置保存并启用账号。'; else if (result.unpaid) message = '发现 ' + result.unpaid + ' 个进行中或待付款周期,按规则未拉取;已存在的未推送待付款记录已清理。'; else if (!result.available && result.filtered) message = 'Walmart 返回了报告日期,但所选范围内没有可拉取的历史结算单。'; else if (!result.available) message = '已扫描 ' + result.accounts + ' 个账号,但 Walmart 官方没有返回可用结算数据。'; else message = '扫描 ' + result.accounts + ' 个账号,' + (usedStatement ? 'Payment Statement' : 'Recon 报告') + ' ' + result.available + ' 个,新增 ' + (result.saved || 0) + ' 份,已存在 ' + (result.skipped || 0) + ' 份。'; toast(message); loadWalmartSettlements(false); } catch (error) { toast('同步失败:' + error.message); } finally { hideLoading(); } }; window.clearWalmartFailedMessages = async function () { const ids = Array.from(document.querySelectorAll('[data-wm-settlement-id]:checked')).map(item => Number(item.dataset.wmSettlementId)); if (!ids.length) return loadWalmartSettlements(false); try { const result = await apiPost('/api/walmart-settlement/settlements/clear-failed-messages', { ids }); toast('已清除 ' + result.cleared + ' 条失败信息'); loadWalmartSettlements(false); } catch (error) { toast('刷新失败:' + error.message); } }; window.pushSelectedWalmartKingdee = async function () { const selected = Array.from(document.querySelectorAll('[data-wm-settlement-id]:checked')).map(input => ({ id: Number(input.dataset.wmSettlementId), account: input.dataset.wmSettlementAccount, status: input.dataset.wmSettlementStatus })); if (!selected.length) return toast('请先勾选要推送的结算单'); const targets = selected.filter(item => item.status !== 'success'); if (!targets.length) return toast('所选结算单均已推送,请先检查金蝶单据'); if (!confirm('确认推送选中的 ' + targets.length + ' 张结算单到金蝶?')) return; let success = 0, failed = 0, skipped = selected.length - targets.length; showLoading('正在推送选中的结算单...'); try { for (const item of targets) { try { const result = await apiPost('/api/walmart-settlement/settlements/push', { settlement_id: item.id, account_code: item.account }); if (result.ok) success += 1; else if (result.status === 'skipped') skipped += 1; else failed += 1; } catch (error) { failed += 1; } } toast('推送完成:成功 ' + success + ' 张,跳过 ' + skipped + ' 张,失败 ' + failed + ' 张'); loadWalmartSettlements(false); } finally { hideLoading(); } }; window.pushWalmartKingdee = async function (id, account) { if (!confirm('确认将这张沃尔玛结算单推送到金蝶?')) return; try { showLoading('正在推送金蝶...'); const result = await apiPost('/api/walmart-settlement/settlements/push', { settlement_id: id, account_code: account }); toast(result.message || (result.ok ? '推送成功' : '推送失败')); loadWalmartSettlements(false); } catch (error) { toast('推送失败:' + error.message); } finally { hideLoading(); } }; window.checkWalmartKingdee = async function (id, account) { try { showLoading('正在检查金蝶单据...'); const result = await apiPost('/api/walmart-settlement/settlements/' + id + '/check?account_code=' + encodeURIComponent(account)); toast(result.message); loadWalmartSettlements(false); } catch (error) { toast('检查失败:' + error.message); } finally { hideLoading(); } }; function walmartMoney(value, currency) { const amount = Number(value || 0); const symbol = currency === 'USD' ? '$' : (currency || ''); return `${amount < 0 ? '-' : ''}${symbol} ${moneyText(Math.abs(amount))}`.trim(); } function walmartDetailTitle(groupLabel, itemLabel) { if (itemLabel) return itemLabel; if (groupLabel === 'Services fees') return 'Services Fees'; return groupLabel || '结算明细'; } function walmartFeeDetail(row) { const raw = String(row.description || row.amount_field || '').trim(); const key = raw.toLowerCase().replace(/[^a-z0-9]+/g, ''); const labels = { wfsfulfillmentfee: 'WFS Fulfillment Fee', wfsreturnprocessingfee: 'Return Processing Fee', wfsreturneditemdisposalfee: 'Returned Item Disposal Fee', wfslostinventory: 'Found Inventory', wfsstoragefee: Number(row.amount || 0) > 0 ? 'Discounts - Storage Fee' : 'Storage Fee', wfslongtermstoragefee: 'Long Term Storage Fee', wfsinboundtransportationfee: 'Inbound Transportation Fee' }; if (labels[key]) return labels[key]; return raw.replace(/^WFS\s*/i, '').replace(/([a-z])([A-Z])/g, '$1 $2') || row.amount_field || '-'; } function mergeWalmartServiceDetails(rows) { const merged = new Map(); rows.forEach(row => { const feeDetail = walmartFeeDetail(row); const current = merged.get(feeDetail) || { ...row, amount: 0, detail_count: 0 }; current.amount += Number(row.amount || 0); current.detail_count += 1; if (!current.transaction_date && row.transaction_date) current.transaction_date = row.transaction_date; merged.set(feeDetail, current); }); return Array.from(merged.values()); } function renderWalmartDetailHeader(isServices) { wmDetailHead.innerHTML = isServices ? '服务费用明细扣费日期金额状态' : '服务费用明细扣费日期订单号采购订单号SKU金额状态交易类型'; } function walmartEnglishDate(value) { const text = String(value || '').slice(0, 10); const parts = text.split('-').map(Number); if (parts.length !== 3 || !parts[0]) return text || '-'; return new Date(parts[0], parts[1] - 1, parts[2]).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }); } window.loadWalmartSummary = async function () { const query = new URLSearchParams({ page: 1, page_size: 10 }); if (state.summary.settlementId) query.set('settlement_id', state.summary.settlementId); try { const data = await apiGet('/api/walmart-settlement/settlements/summary?' + query); if (!data.settlement) { wmSummaryMeta.textContent = '未找到结算单。'; wmSummarySections.innerHTML = '
请先从结算单列表选择一张结算单。
'; return; } const settlement = data.settlement; state.summary.settlementId = settlement.id; state.summary.settlement = settlement; const period = [settlement.period_start, settlement.period_end].filter(Boolean).join(' - ') || settlement.report_date || '-'; wmSummaryMeta.textContent = `结算单编号:${settlement.settlement_number} · 周期:${period} · 结算金额:${walmartMoney(data.official_total, settlement.currency)}`; wmSummarySections.innerHTML = (data.groups || []).map(group => { const items = (group.items || []).filter(item => item.item_key !== 'total'); const itemRows = items.map(item => `
${walmartMoney(item.amount, settlement.currency)}
`).join(''); const negative = Number(group.amount || 0) < 0 ? ' negative' : ''; return `

${escHtml(group.group_label)}

${itemRows}
Total:${walmartMoney(group.amount, settlement.currency)}
`; }).join(''); } catch (error) { toast('Unable to load settlement summary: ' + error.message); } }; window.showWalmartSettlementSummary = function (id, number, account) { state.summary = { settlementId: id, number: number || '', account: account || '', settlement: null }; openWalmartPage('walmart-settlement-summary'); }; window.openWalmartSummaryDetails = function (groupKey, itemKey, groupLabel, itemLabel) { const settlement = state.summary.settlement || {}; state.detail = { id: state.summary.settlementId, number: settlement.settlement_number || state.summary.number || '', account: settlement.account_code || state.summary.account || '', groupKey: groupKey || '', itemKey: itemKey || '', groupLabel: groupLabel || '', itemLabel: itemLabel || '', paymentStatus: settlement.payment_status || '', page: 1, pageSize: 100, pages: 1 }; wmDetailKeyword.value = ''; wmDetailDateMin.value = ''; wmDetailDateMax.value = ''; openWalmartPage('walmart-settlement-details'); }; window.showWalmartSettlementDetail = function (id, number, account) { state.summary = { settlementId: id, number: number || '', account: account || '', settlement: null }; openWalmartSummaryDetails('', '', '全部明细', ''); }; window.backToWalmartSummary = function () { openWalmartPage('walmart-settlement-summary'); }; window.loadWalmartDetails = async function (reset) { if (!state.detail.id) return; if (reset) state.detail.page = 1; const query = new URLSearchParams({ settlement_id: state.detail.id, page: state.detail.page, page_size: state.detail.groupKey === 'services_fees' ? 500 : state.detail.pageSize }); if (state.detail.groupKey) query.set('group_key', state.detail.groupKey); if (state.detail.itemKey) query.set('item_key', state.detail.itemKey); if (wmDetailKeyword.value.trim()) query.set('keyword', wmDetailKeyword.value.trim()); if (wmDetailDateMin.value) query.set('date_min', wmDetailDateMin.value); if (wmDetailDateMax.value) query.set('date_max', wmDetailDateMax.value); try { const data = await apiGet('/api/walmart-settlement/settlements/summary?' + query); const settlement = data.settlement || {}; state.detail.paymentStatus = settlement.payment_status || state.detail.paymentStatus; wmDetailTitle.textContent = walmartDetailTitle(state.detail.groupLabel, state.detail.itemLabel); const isServices = state.detail.groupKey === 'services_fees'; wmDetailSubtitle.textContent = isServices ? '查看 Walmart fulfillment services 的费用明细。' : `结算单编号:${settlement.settlement_number || state.detail.number || ''} · ${state.detail.groupLabel || '全部明细'}`; const status = String(state.detail.paymentStatus || '').toLowerCase() === 'paid' ? 'Paid' : 'Pending'; const displayRows = isServices ? mergeWalmartServiceDetails(data.details || []) : (data.details || []); renderWalmartDetailHeader(isServices); wmDetailTb.innerHTML = displayRows.map(row => { const service = row.item_key === 'walmart_fulfillment_services' ? 'WFS' : row.item_key === 'sem_marketing' ? 'SEM' : row.item_key === 'walmart_product_advertising' ? 'Advertising' : row.item_key === 'review_accelerator' ? 'Review' : (row.group_label || row.transaction_type || '-'); const base = `${escHtml(service)}${escHtml(walmartFeeDetail(row))}${escHtml(walmartEnglishDate(row.transaction_date))}`; if (isServices) return `${base}${walmartMoney(row.amount, row.currency || settlement.currency)}${status}`; return `${base}${escHtml(row.order_number || '-')}${escHtml(row.purchase_order_id || '-')}${escHtml(row.sku || '-')}${walmartMoney(row.amount, row.currency || settlement.currency)}${status}${escHtml(row.transaction_type || '-')}`; }).join('') || `
暂无匹配明细。
`; const detailTotal = isServices ? displayRows.length : data.detail_total; state.detail.pages = Math.max(1, Math.ceil(detailTotal / data.page_size)); wmDetailPageInfo.textContent = `第 ${data.page} / ${state.detail.pages} 页,共 ${detailTotal} 条`; } catch (error) { toast('加载结算明细失败:' + error.message); } }; window.changeWalmartDetailPage = function (delta) { state.detail.page = Math.min(state.detail.pages || 1, Math.max(1, state.detail.page + delta)); loadWalmartDetails(false); }; async function downloadWithAuth(url, filename) { const response = await fetch(url, { headers: authHeaders() }); if (!response.ok) throw new Error('导出失败:' + response.status); const blob = await response.blob(); const link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = filename; document.body.appendChild(link); link.click(); setTimeout(() => { URL.revokeObjectURL(link.href); link.remove(); }, 1000); } window.exportWalmartSummary = async function (format) { if (!state.summary.settlementId) return toast('请先打开一张结算单'); const suffix = format === 'pdf' ? 'pdf' : 'xlsx'; const number = (state.summary.settlement && state.summary.settlement.settlement_number) || state.summary.number || state.summary.settlementId; try { await downloadWithAuth(`/api/walmart-settlement/settlements/${state.summary.settlementId}/summary-export?format=${suffix}`, `walmart_settlement_summary_${number}.${suffix}`); toast('汇总导出成功'); } catch (error) { toast(error.message); } }; window.exportWalmartDetails = async function () { if (!state.detail.id) return toast('请先打开一张结算单'); try { await downloadWithAuth(`/api/walmart-settlement/settlements/${state.detail.id}/export`, state.detail.number + '.xlsx'); toast('导出成功'); } catch (error) { toast(error.message); } }; window.loadWalmartFeeMappings = async function () { try { const [types, saved] = await Promise.all([apiGet('/api/walmart-settlement/types'), apiGet('/api/walmart-settlement/fee-mappings')]); const map = new Map((saved.rows || []).map(item => [item.transaction_type + '\u0000' + item.amount_field, item])); wmFeeMappingTb.innerHTML = (types.rows || []).map(type => { const savedItem = map.get(type.transaction_type + '\u0000' + type.amount_field) || {}; const item = Object.keys(savedItem).length ? savedItem : (type.mapping || {}); const scope = type.mapping_scope === 'fee_detail' ? 'Fee Detail' : '汇总项目'; return `${escHtml(type.transaction_type)}${escHtml(type.amount_field)}${scope}`; }).join('') || '
同步到有金额的结算明细后,这里会自动出现待映射项
'; } catch (error) { toast('加载费用项映射失败:' + error.message); } }; window.saveWalmartFeeMappings = async function () { const rows = Array.from(wmFeeMappingTb.querySelectorAll('tr[data-type]')).map(tr => ({ transaction_type: tr.dataset.type, amount_field: tr.dataset.field, fee_item: tr.querySelector('[data-key=fee_item]').value.trim(), fee_item_field: tr.querySelector('[data-key=fee_item_field]').value.trim(), amount_target_field: tr.querySelector('[data-key=amount_target_field]').value.trim(), enabled: tr.querySelector('[data-key=enabled]').checked ? 1 : 0 })); try { const result = await apiPost('/api/walmart-settlement/fee-mappings', { rows }); toast('已保存 ' + result.saved + ' 条费用项映射'); } catch (error) { toast('保存失败:' + error.message); } }; const sourceOptions = ['结算单编号','结算日期','账号编码','交易类型','金额字段','金额','币种','订单号','交易开始日期','交易结束日期']; function renderFieldMappings(fields, saved) { const savedMap = new Map((saved || []).map(item => [item.target_key + '\u0000' + item.field_scope + '\u0000' + (item.entry_key || ''), item])); wmFieldMappingTb.innerHTML = (fields || []).map(field => { const item = savedMap.get(field.key + '\u0000' + field.scope + '\u0000' + (field.entry_key || '')) || {}; const source = item.source_col || ''; return `${escHtml(field.key)}${escHtml(field.name || '')}${field.scope === 'head' ? '单据头' : '明细'}${escHtml(field.entry_key || '')}${escHtml(field.parent_entry_key || '')}`; }).join('') + `${sourceOptions.map(value => ``; } window.loadWalmartKingdeeFields = async function () { const account = wmFieldAccount.value; if (!account) return toast('请选择当前账号'); try { showLoading('正在读取金蝶字段...'); const [fields, saved] = await Promise.all([apiGet('/api/walmart-settlement/kingdee-fields?account_code=' + encodeURIComponent(account)), apiGet('/api/walmart-settlement/field-mappings?account_code=' + encodeURIComponent(account))]); if (!fields.ok) throw new Error(fields.message); state.kingdeeFields = fields.fields || []; renderFieldMappings(state.kingdeeFields, saved.rows || []); } catch (error) { toast('读取失败:' + error.message); } finally { hideLoading(); } }; window.loadWalmartFieldMappings = async function () { if (!state.accounts.length) await loadWalmartAccounts(); const account = wmFieldAccount.value; if (!account) return; try { const saved = await apiGet('/api/walmart-settlement/field-mappings?account_code=' + encodeURIComponent(account)); if (state.kingdeeFields.length) renderFieldMappings(state.kingdeeFields, saved.rows || []); else if ((saved.rows || []).length) renderFieldMappings(saved.rows.map(item => ({ key: item.target_key, name: item.target_name, scope: item.field_scope, entry_key: item.entry_key, parent_entry_key: item.parent_entry_key })), saved.rows); } catch (error) { toast('加载字段映射失败:' + error.message); } }; window.saveWalmartFieldMappings = async function () { const account = wmFieldAccount.value; if (!account) return toast('请选择当前账号'); const mappings = Array.from(wmFieldMappingTb.querySelectorAll('tr[data-target]')).map((tr, index) => { const type = tr.querySelector('[data-key=value_type]').value; const value = tr.querySelector('[data-key=source]').value.trim(); return { target_key: tr.dataset.target, target_name: tr.dataset.name, field_scope: tr.dataset.scope, value_type: type, source_col: type === 'source' ? value : '', const_value: type === 'const' ? value : '', is_base: tr.querySelector('[data-key=is_base]').checked ? 1 : 0, entry_key: tr.dataset.entry, parent_entry_key: tr.dataset.parent, enabled: tr.querySelector('[data-key=enabled]').checked ? 1 : 0, sort_order: index }; }); try { const result = await apiPost('/api/walmart-settlement/field-mappings', { account_code: account, mappings }); toast('已保存 ' + result.saved + ' 条字段映射'); } catch (error) { toast('保存失败:' + error.message); } }; window.copyWalmartFieldMappings = async function () { const source = wmFieldCopySource.value, target = wmFieldAccount.value; if (!source || !target) return toast('请选择复制来源账号和当前目标账号'); if (!confirm('确认用 ' + source + ' 的映射替换 ' + target + ' 当前映射?')) return; try { const result = await apiPost('/api/walmart-settlement/field-mappings/copy', { source_account_code: source, target_account_code: target }); toast('已复制 ' + result.copied + ' 条映射'); loadWalmartFieldMappings(); } catch (error) { toast('复制失败:' + error.message); } }; window.loadWalmartSchedule = async function () { try { const data = await apiGet('/api/walmart-settlement/schedule'); wmScheduleEnabled.value = String(data.enabled || 0); wmScheduleCron.value = data.cron || '0 */2 * * *'; wmScheduleNext.value = data.next_run_at || '未安排'; wmScheduleStatus.textContent = `上次执行:${data.last_run_at || '无'};状态:${data.last_run_status || '未执行'};${data.last_run_message || ''}`; } catch (error) { toast('加载定时配置失败:' + error.message); } }; window.saveWalmartSchedule = async function () { try { const data = await apiPost('/api/walmart-settlement/schedule', { enabled: Number(wmScheduleEnabled.value), cron: wmScheduleCron.value.trim() }); toast('定时拉取配置已保存'); wmScheduleNext.value = data.next_run_at || '未安排'; } catch (error) { toast('保存失败:' + error.message); } }; window.runWalmartScheduleNow = async function () { try { showLoading('正在发现沃尔玛结算报告...'); const result = await apiPost('/api/walmart-settlement/schedule/run', {}); toast('执行完成:新增 ' + (result.saved || 0) + ' 份报告'); loadWalmartSchedule(); } catch (error) { toast('执行失败:' + error.message); } finally { hideLoading(); } }; function walmartFieldValueOptions(current) { const values = sourceOptions.slice(); if (current && !values.includes(current)) values.unshift(current); return '' + values.map(value => ``).join(''); } function syncWalmartFieldMappings() { state.walmartFieldRows = Array.from(wmFieldMappingTb.querySelectorAll('tr[data-wm-field-row]')).map(tr => { const read = key => tr.querySelector(`[data-key="${key}"]`); const type = read('value_type')?.value || 'source'; const value = read('source')?.value || ''; return { target_key: read('target_key')?.value.trim() || '', target_name: read('target_name')?.value.trim() || '', field_scope: read('field_scope')?.value || 'entry', value_type: type, source_col: type === 'source' ? value : '', const_value: type === 'const' ? value : '', is_base: read('is_base')?.checked ? 1 : 0, entry_key: read('entry_key')?.value.trim() || 'FEntity', parent_entry_key: read('parent_entry_key')?.value.trim() || '', enabled: read('enabled')?.checked ? 1 : 0 }; }); return state.walmartFieldRows; } window.onWalmartFieldValueTypeChange = function () { syncWalmartFieldMappings(); renderFieldMappings([], state.walmartFieldRows); }; function renderFieldMappings(fields, saved) { const savedRows = saved || []; const rows = (fields || []).map(field => { const item = savedRows.find(row => row.target_key === field.key && (row.entry_key || '') === (field.entry_key || '')) || savedRows.find(row => row.target_key === field.key) || {}; return { target_key: item.target_key || field.key || '', target_name: item.target_name || field.name || '', field_scope: item.field_scope || field.scope || 'entry', value_type: item.value_type || 'source', source_col: item.source_col || '', const_value: item.const_value || '', is_base: item.is_base ? 1 : 0, entry_key: item.entry_key || field.entry_key || 'FEntity', parent_entry_key: item.parent_entry_key || field.parent_entry_key || '', enabled: item.enabled === 0 ? 0 : 1 }; }); if (!fields || !fields.length) rows.push(...savedRows.filter(item => !rows.some(row => row.target_key === item.target_key))); state.walmartFieldRows = rows; wmFieldMappingTb.innerHTML = rows.map((row, index) => { const value = row.value_type === 'const' ? row.const_value : row.source_col; const sourceCell = row.value_type === 'auto' ? '' : row.value_type === 'source' ? `` : ``; return `${sourceCell}`; }).join('') || '
暂无字段,请先从金蝶拉取字段
'; } window.deleteWalmartFieldMappingRow = function (index) { syncWalmartFieldMappings(); state.walmartFieldRows.splice(index, 1); renderFieldMappings([], state.walmartFieldRows); }; window.addWalmartFieldMappingRow = function () { syncWalmartFieldMappings(); state.walmartFieldRows.push({ target_key: '', target_name: '', field_scope: 'entry', value_type: 'source', source_col: '', const_value: '', is_base: 0, entry_key: 'FEntity', parent_entry_key: '', enabled: 1 }); renderFieldMappings([], state.walmartFieldRows); }; window.saveWalmartFieldMappings = async function () { const account = wmFieldAccount.value; if (!account) return toast('请选择当前账号'); const mappings = syncWalmartFieldMappings().filter(row => row.target_key); try { const result = await apiPost('/api/walmart-settlement/field-mappings', { account_code: account, mappings }); toast('已保存 ' + result.saved + ' 条字段映射'); } catch (error) { toast('保存失败:' + error.message); } }; window.loadWalmartKingdeeFields = async function () { const account = wmFieldAccount.value; if (!account) return toast('请选择当前账号'); try { showLoading('正在读取金蝶字段...'); const [fields, saved] = await Promise.all([apiGet('/api/walmart-settlement/kingdee-fields?account_code=' + encodeURIComponent(account)), apiGet('/api/walmart-settlement/field-mappings?account_code=' + encodeURIComponent(account))]); if (!fields.ok) throw new Error(fields.message); state.kingdeeFields = fields.fields || []; renderFieldMappings(state.kingdeeFields, saved.rows || []); toast('已按结算单字段映射规则读取 ' + state.kingdeeFields.length + ' 个字段'); } catch (error) { toast('读取失败:' + error.message); } finally { hideLoading(); } }; function installWalmartFieldMappingParity() { const table = document.querySelector('#walmart-field-mapping table'); const head = table?.querySelector('thead tr'); if (head) head.innerHTML = '金蝶字段标识金蝶名称字段层级取值方式取值(来源列 / 固定值)分录Key父分录Key基础资料启用操作'; const card = document.querySelector('#walmart-field-mapping .card'); if (card && !document.getElementById('wmAddFieldMapping')) card.insertAdjacentHTML('beforeend', '
'); } function walmartMappingBadge(status) { if (!status) return ''; const missing = status === '未配置'; return `${status}`; } function mergeWalmartServiceDetails(rows) { const merged = new Map(); rows.forEach(row => { const feeDetail = walmartFeeDetail(row); const current = merged.get(feeDetail) || { ...row, amount: 0, detail_count: 0, mapping_status: '已配置' }; current.amount += Number(row.amount || 0); current.detail_count += 1; if (row.mapping_status === '未配置') current.mapping_status = '未配置'; if (!current.transaction_date && row.transaction_date) current.transaction_date = row.transaction_date; merged.set(feeDetail, current); }); return Array.from(merged.values()); } function renderWalmartDetailHeader(isServices) { wmDetailHead.innerHTML = isServices ? '鏈嶅姟璐圭敤鏄庣粏鏄惁鏄犲皠鎵h垂鏃ユ湡閲戦鐘舵€?/th>' : '鏈嶅姟璐圭敤鏄庣粏鏄惁鏄犲皠鎵h垂鏃ユ湡璁㈠崟鍙?/th>閲囪喘璁㈠崟鍙?/th>SKU閲戦鐘舵€?/th>浜ゆ槗绫诲瀷'; } window.loadWalmartSummary = async function () { const query = new URLSearchParams({ page: 1, page_size: 10 }); if (state.summary.settlementId) query.set('settlement_id', state.summary.settlementId); try { const data = await apiGet('/api/walmart-settlement/settlements/summary?' + query); if (!data.settlement) { wmSummarySections.innerHTML = '
请先从结算单列表选择一张结算单。
'; return; } const settlement = data.settlement; state.summary.settlementId = settlement.id; state.summary.settlement = settlement; const period = [settlement.period_start, settlement.period_end].filter(Boolean).join(' - ') || settlement.report_date || '-'; wmSummaryMeta.textContent = `结算单编号:${settlement.settlement_number} · 周期:${period} · 结算金额:${walmartMoney(data.official_total, settlement.currency)}`; wmSummarySections.innerHTML = (data.groups || []).map(group => { const items = (group.items || []).filter(item => item.item_key !== 'total'); const itemRows = items.map(item => `
${walmartMoney(item.amount, settlement.currency)}
`).join(''); const negative = Number(group.amount || 0) < 0 ? ' negative' : ''; return `

${escHtml(group.group_label)}${walmartMappingBadge(group.mapping_status)}

${itemRows}
Total:${walmartMappingBadge(group.mapping_status)}${walmartMoney(group.amount, settlement.currency)}
`; }).join(''); } catch (error) { toast('Unable to load settlement summary: ' + error.message); } }; window.loadWalmartDetails = async function (reset) { if (!state.detail.id) return; if (reset) state.detail.page = 1; const query = new URLSearchParams({ settlement_id: state.detail.id, page: state.detail.page, page_size: state.detail.groupKey === 'services_fees' ? 500 : state.detail.pageSize }); if (state.detail.groupKey) query.set('group_key', state.detail.groupKey); if (state.detail.itemKey) query.set('item_key', state.detail.itemKey); if (wmDetailKeyword.value.trim()) query.set('keyword', wmDetailKeyword.value.trim()); if (wmDetailDateMin.value) query.set('date_min', wmDetailDateMin.value); if (wmDetailDateMax.value) query.set('date_max', wmDetailDateMax.value); try { const data = await apiGet('/api/walmart-settlement/settlements/summary?' + query); const settlement = data.settlement || {}; state.detail.paymentStatus = settlement.payment_status || state.detail.paymentStatus; wmDetailTitle.textContent = walmartDetailTitle(state.detail.groupLabel, state.detail.itemLabel); const isServices = state.detail.groupKey === 'services_fees'; wmDetailSubtitle.textContent = isServices ? '鏌ョ湅 Walmart fulfillment services 鐨勮垂鐢ㄦ槑缁嗐€?' : `缁撶畻鍗曠紪鍙凤細${settlement.settlement_number || state.detail.number || ''}`; const status = String(state.detail.paymentStatus || '').toLowerCase() === 'paid' ? 'Paid' : 'Pending'; const displayRows = isServices ? mergeWalmartServiceDetails(data.details || []) : (data.details || []); renderWalmartDetailHeader(isServices); wmDetailTb.innerHTML = displayRows.map(row => { const service = row.item_key === 'walmart_fulfillment_services' ? 'WFS' : row.item_key === 'sem_marketing' ? 'SEM' : row.item_key === 'walmart_product_advertising' ? 'Advertising' : row.item_key === 'review_accelerator' ? 'Review' : (row.group_label || row.transaction_type || '-'); const base = `${escHtml(service)}${escHtml(walmartFeeDetail(row))}${walmartMappingBadge(row.mapping_status) || '未配置'}${escHtml(walmartEnglishDate(row.transaction_date))}`; if (isServices) return `${base}${walmartMoney(row.amount, row.currency || settlement.currency)}${status}`; return `${base}${escHtml(row.order_number || '-')}${escHtml(row.purchase_order_id || '-')}${escHtml(row.sku || '-')}${walmartMoney(row.amount, row.currency || settlement.currency)}${status}${escHtml(row.transaction_type || '-')}`; }).join('') || `
鏆傛棤鍖归厤鏄庣粏銆?/div>`; const detailTotal = isServices ? displayRows.length : data.detail_total; state.detail.pages = Math.max(1, Math.ceil(detailTotal / data.page_size)); wmDetailPageInfo.textContent = 'Page ' + data.page + ' / ' + state.detail.pages + ', total ' + detailTotal; } catch (error) { toast('加载结算明细失败:' + error.message); } }; function installPermissions() { const original = window.applyNavPermissions; window.applyNavPermissions = function () { if (original) original(); const map = { 'walmart-api-config':'walmart_settlement.api_config', 'walmart-settlements':'walmart_settlement.settlements', 'walmart-settlement-summary':'walmart_settlement.summary', 'walmart-settlement-details':'walmart_settlement.details', 'walmart-fee-mapping':'walmart_settlement.fee_mapping', 'walmart-field-mapping':'walmart_settlement.field_mapping', 'walmart-schedule':'walmart_settlement.schedule' }; let visible = false; document.querySelectorAll('#navWalmartSettlementSub [data-s]').forEach(item => { const allowed = typeof hasResource !== 'function' || hasResource(map[item.dataset.s]) || (item.dataset.s === 'walmart-settlement-summary' && hasResource('walmart_settlement.details')); item.style.display = allowed ? '' : 'none'; if (allowed) visible = true; }); document.getElementById('navWalmartSettlement').style.display = visible ? '' : 'none'; }; } function hideLegacyWalmartKingdeeAccountFields() { ['wmFormId', 'wmEntryKey'].forEach(id => { const input = document.getElementById(id); if (input && input.parentElement) input.parentElement.style.display = 'none'; }); const table = document.querySelector('#walmart-api-config table'); if (table) { table.querySelectorAll('th:nth-child(6), td:nth-child(6)').forEach(cell => { cell.style.display = 'none'; }); } const mappingTable = document.getElementById('wmFeeMappingTb'); if (mappingTable && !mappingTable.dataset.multiFieldHints) { mappingTable.dataset.multiFieldHints = '1'; const applyHints = () => { mappingTable.querySelectorAll('[data-key="fee_item_field"]').forEach(input => { input.placeholder = 'F_PAEZ_FYX,F_PAEZ_Base'; input.title = '多个字段用逗号、分号或换行分隔,会同时写入同一行'; }); mappingTable.querySelectorAll('[data-key="amount_target_field"]').forEach(input => { input.placeholder = 'FAMOUNT,F_PAEZ_Amount'; input.title = '多个字段用逗号、分号或换行分隔,会同时写入同一行'; }); }; new MutationObserver(applyHints).observe(mappingTable, { childList: true, subtree: true }); applyHints(); } } installStyle(); installNavigation(); installPages(); hideLegacyWalmartKingdeeAccountFields(); installWalmartFieldMappingParity(); installPermissions(); if (window.currentUser && window.applyNavPermissions) window.applyNavPermissions(); })();