/* Shopify Payouts extensions: mapping pages, push controls and mapping status. */
(function () {
'use strict';
const escHtml = (v) => (window.esc ? window.esc(v) : String(v == null ? '' : v).replace(/[&<>"']/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c])));
const val = (id) => (document.getElementById(id) || {}).value || '';
const notify = (msg) => window.toast ? window.toast(msg) : alert(msg);
let payoutPageRows = [];
function stores() { return apiGet('/api/shopify/stores').then(r => Array.isArray(r) ? r : (r.stores || [])); }
function storeOptions(rows) { return '' + rows.map(s => '').join(''); }
async function populateStoreOptions(ids) { const rows = await stores(); ids.forEach(id => { const el = document.getElementById(id); if (!el) return; const current = el.value; el.innerHTML = storeOptions(rows); if (rows.some(row => row.code === current)) el.value = current; }); return rows; }
const payoutNavResources = {
'shopify-payout-detail': 'shopify.payout_detail',
'shopify-payout-raw': 'shopify.payout_raw',
'shopify-payout-mapping': 'shopify.payout_mapping',
'shopify-payout-field-map': 'shopify.payout_field_mapping'
};
function addNav(id, label) { const sub = document.getElementById('navShopifySub'); if (!sub || sub.querySelector('[data-s="' + id + '"]')) return; const n = document.createElement('div'); n.className = 'nav navchild'; n.dataset.s = id; n.dataset.resource = payoutNavResources[id] || 'shopify'; n.textContent = label; n.onclick = () => window.showSection(id); sub.appendChild(n); }
function installPayoutPermissions() {
if (window._shopifyPayoutPermissionWrapped || typeof window.applyNavPermissions !== 'function') return;
const original = window.applyNavPermissions;
window.applyNavPermissions = function () {
original();
Object.keys(payoutNavResources).forEach(id => {
const resource = payoutNavResources[id];
const allowed = typeof window.hasResource !== 'function' || window.hasResource(resource);
const nav = document.querySelector('#navShopifySub [data-s="' + id + '"]');
if (nav) nav.style.display = allowed ? '' : 'none';
const section = document.getElementById(id);
if (section && !allowed) section.classList.remove('active');
});
const sub = document.getElementById('navShopifySub');
const group = document.getElementById('navShopify');
if (sub && group) {
const visible = Array.prototype.some.call(
sub.querySelectorAll('.nav[data-s]'),
item => item.style.display !== 'none'
);
group.style.display = visible ? '' : 'none';
}
};
window._shopifyPayoutPermissionWrapped = true;
}
function payoutTypeHint(type) {
const raw = canonicalType(type);
const hints = {
reserve: 'Reserved Funds(页面名称)',
dispute: 'Chargeback / Chargeback Hold Release(按金额正负显示)',
debit: 'Debit;若 source_type=adjustment 且 adjustment_reason=chargeback_fee,页面显示 Chargeback Fee',
credit: 'Credit;若 source_type=adjustment 且 adjustment_reason=chargeback_fee,页面显示 Chargeback Fee Refund',
adjustment: 'Adjustment(具体名称取决于 adjustment_reason)',
charge: 'Charge',
refund: 'Refund',
payout_failure: 'Payout failure',
refund_failure: 'Refund failure',
payout: 'Payout(汇总行,通常不参与明细映射)',
};
return hints[raw] || '';
}
function canonicalType(type) {
const raw = String(type || '').trim().toLowerCase().replace(/[- ]/g, '_');
return ({ reserved_funds: 'reserve', chargeback: 'dispute', chargeback_hold_release: 'dispute', chargeback_fee: 'debit', chargeback_fee_refund: 'credit' })[raw] || raw;
}
async function refreshPayoutTypeFilter(storeCode) {
const select = document.getElementById('shopifyPayoutDetailTypeFilter');
if (!select || !storeCode) return;
const current = select.value;
try {
const rawTypes = (await apiGet('/api/shopify/payouts/types?store_code=' + encodeURIComponent(storeCode))).rows || [];
const types = [...new Set(rawTypes.map(x => String(x || '').trim()).filter(Boolean))];
select.innerHTML = '' + types.map(type => '').join('');
if (types.includes(current)) select.value = current;
} catch (e) {
// The detail request itself remains usable if the optional filter refresh fails.
}
}
function mappingPageHtml() { return 'Payouts 映射关系
全部 Shopify 店铺共用一套原始 API Type + 实际非零金额类型映射。
| 原始 API Type | 系统类型 | 金额类型 | 金蝶费用项编码 | 费用项字段(可多个) | 金额字段(可多个) | 启用 | 操作 |
|---|
'; }
function fieldPageHtml() { return `金蝶单据字段映射
按结算单字段映射界面维护 Payouts 单据头和分录字段。
说明:区分单据头/分录;取值方式可选源列、固定值或金蝶自动生成;基础资料字段会自动按编码封装。分录 Key 与父分录 Key 的填写方式与“结算单字段映射”一致。
| 金蝶字段标识 | 金蝶名称 | 字段层级 | 取值方式 | 取值(来源列/固定值) | 分录 Key | 父分录 Key | 基础资料 | 启用 | 操作 |
|---|
`; }
function rawPageHtml() { return 'Payouts API 原始明细
仅展示 Shopify API 原始返回的 balance transaction,不参与现有业务归类或金蝶推送。
原始 Type 用于一比一判断和金蝶映射;例如 reserve、debit、dispute 不会改名。数据来源为已同步到本地的 API 报文。
| Processed at | Payout ID | Transaction ID | 原始 Type | Source type | Adjustment reason | Source order ID | Amount | Fee | Net | Currency |
|---|
'; }
function addFieldRow(row) { row = row || {}; const tb = document.getElementById('spfTb'); const emptyRow = tb?.querySelector('.empty')?.closest('tr'); if (emptyRow) emptyRow.remove(); const tr = document.createElement('tr'); tr.innerHTML = '
| | | | | | | | | | '; tr.querySelector('[data-k="field_scope"]').value = row.field_scope || 'entry'; tr.querySelector('[data-k="value_type"]').value = row.value_type || 'source'; tr.querySelector('button').onclick = () => tr.remove(); tb.appendChild(tr); }
function collectFields() { return [...document.querySelectorAll('#spfTb tr')].map(tr => { const o = {}; tr.querySelectorAll('[data-k]').forEach(x => { o[x.dataset.k] = x.type === 'checkbox' ? (x.checked ? 1 : 0) : x.value.trim(); }); if (o.value_type === 'const') { o.const_value = o.source_col; o.source_col = ''; } return o; }).filter(x => x.target_key); }
async function loadFields() { const storeCode = val('spmFieldStore'); const tb = document.getElementById('spfTb'); if (!storeCode) { if (tb) tb.innerHTML = '请选择店铺后查看对应字段映射 |
'; return; } const d = await apiGet('/api/shopify/payouts/field-mapping?store_code=' + encodeURIComponent(storeCode)); if (tb) { tb.innerHTML = ''; (d.rows || []).forEach(addFieldRow); if (!(d.rows || []).length) tb.innerHTML = '当前店铺暂无字段映射,可从金蝶拉取或手动添加 |
'; } }
async function saveFields() { const storeCode = val('spmFieldStore'); if (!storeCode) return notify('请先选择要维护字段映射的店铺'); await apiPost('/api/shopify/payouts/field-mapping', { store_code: storeCode, mappings: collectFields() }); notify('已保存 ' + (document.getElementById('spmFieldStore')?.selectedOptions[0]?.textContent || storeCode) + ' 的字段映射'); await loadFields(); }
async function pullFields() { const storeCode = val('spmFieldStore'); if (!storeCode) return notify('请先选择要维护字段映射的店铺'); try { const d = await apiGet('/api/shopify/payouts/kingdee-fields'); if (!d.ok) throw new Error(d.message || '拉取失败'); const tb = document.getElementById('spfTb'); if (tb) tb.innerHTML = ''; (d.fields || []).forEach(f => addFieldRow({ target_key: f.key, target_name: f.name || '', field_scope: f.scope === 'head' ? 'head' : 'entry', entry_key: f.entry_key || 'FEntity', parent_entry_key: f.parent_entry_key || '', value_type: 'source', enabled: 1 })); notify('已为当前店铺加载 ' + (d.fields || []).length + ' 个金蝶字段,确认后请保存映射'); } catch (e) { notify('拉取金蝶字段失败:' + e.message); } }
async function refreshFieldCopyTargets() { const source = val('spmFieldStore'), target = document.getElementById('spmCopyTarget'); if (!target) return; const current = target.value; const rows = (await stores()).filter(row => row.code !== source); target.innerHTML = '' + rows.map(row => '').join(''); if (rows.some(row => row.code === current)) target.value = current; }
async function copyFields() { const source = val('spmFieldStore'), target = val('spmCopyTarget'); if (!source) return notify('请先选择已有映射的源店铺'); if (!target) return notify('请选择要接收映射的目标店铺'); const sourceName = document.getElementById('spmFieldStore')?.selectedOptions[0]?.textContent || source, targetName = document.getElementById('spmCopyTarget')?.selectedOptions[0]?.textContent || target; if (!confirm('确认用 ' + sourceName + ' 已保存的字段映射覆盖 ' + targetName + ' 当前映射?')) return; const result = await apiPost('/api/shopify/payouts/field-mapping/copy', { source_store_code: source, target_store_code: target }); notify('已复制 ' + Number(result.copied || 0) + ' 条字段映射到 ' + targetName); }
async function openFieldMappingPage() { try { await populateStoreOptions(['spmFieldStore']); await refreshFieldCopyTargets(); await loadFields(); } catch (e) { notify('加载店铺字段映射失败:' + e.message); } }
async function loadMappingFieldOptions() { const button = document.getElementById('spmKingdeeFields'); if (button) button.disabled = true; try { const d = await apiGet('/api/shopify/payouts/kingdee-fields'); if (!d.ok) throw new Error(d.message || '拉取失败'); const fields = (d.fields || []).filter(f => f.scope === 'entry'); const optionHtml = fields.map(f => '').join(''); const fee = document.getElementById('spmFeeFieldOptions'), amount = document.getElementById('spmAmountFieldOptions'); if (fee) fee.innerHTML = optionHtml; if (amount) amount.innerHTML = optionHtml; notify('已加载 ' + fields.length + ' 个金蝶分录字段;多个目标字段请用逗号分隔'); } catch (e) { notify('加载金蝶字段失败:' + e.message); } finally { if (button) button.disabled = false; } }
function addMappingRow(row) { row = row || {}; const tr = document.createElement('tr'); tr.dataset.id = row.id || ''; const rawType = String(row.transaction_type || ''); const systemType = payoutTypeHint(rawType) || rawType || '-'; tr.innerHTML = ' | ' + escHtml(systemType) + ' | | | | | | | '; tr.querySelector('[data-k="amount_field"]').value = row.amount_field || 'amount'; tr.querySelector('button').onclick = async () => { if (tr.dataset.id) { try { await apiDelete('/api/shopify/payouts/mapping/' + tr.dataset.id); } catch (e) { notify(e.message); return; } } tr.remove(); }; document.getElementById('spmTb').appendChild(tr); }
function collectMappings() { return [...document.querySelectorAll('#spmTb tr')].map(tr => { const o = {}; tr.querySelectorAll('[data-k]').forEach(x => { o[x.dataset.k] = x.type === 'checkbox' ? (x.checked ? 1 : 0) : x.value.trim(); }); return o; }); }
function updateMappingSummary(sourceTypeCount, addedRowCount, combinationCount) { const rows = [...document.querySelectorAll('#spmTb tr')]; const typeCount = new Set(rows.map(tr => valFrom(tr, 'transaction_type').trim()).filter(Boolean)).size; const el = document.getElementById('spmTypeSummary'); if (!el) return; if (Number.isFinite(sourceTypeCount)) { el.textContent = '全部店铺合计 ' + sourceTypeCount + ' 个原始 Type、' + combinationCount + ' 个非零金额组合;当前显示 ' + typeCount + ' 个原始 Type、' + rows.length + ' 条共用映射,本次新增 ' + addedRowCount + ' 条'; } else { el.textContent = '当前已保存 ' + typeCount + ' 个原始 Type、' + rows.length + ' 条共用映射'; } }
async function loadMappings() { const d = await apiGet('/api/shopify/payouts/mapping?store_code=' + encodeURIComponent(val('spmStore'))); const tb = document.getElementById('spmTb'); if (tb) { tb.innerHTML = ''; (d.rows || []).forEach(addMappingRow); updateMappingSummary(); } }
async function saveMappings() { await apiPost('/api/shopify/payouts/mapping', { store_code: '', rows: collectMappings() }); notify('共用映射已保存'); await loadMappings(); }
async function loadTypes() { const button = document.getElementById('spmTypes'); if (button) button.disabled = true; try { const data = await apiGet('/api/shopify/payouts/types'); const types = [...new Set((data.rows || []).map(type => String(type || '').trim()).filter(Boolean))]; const combinations = data.combinations || []; const existing = new Set([...document.querySelectorAll('#spmTb tr')].map(tr => valFrom(tr, 'transaction_type').trim().toLowerCase() + '+' + valFrom(tr, 'amount_field'))); let added = 0; combinations.forEach(item => { const type = String(item.transaction_type || '').trim(), amount = String(item.amount_field || '').trim().toLowerCase(), key = type.toLowerCase() + '+' + amount; if (type && ['amount', 'fee', 'net'].includes(amount) && !existing.has(key)) { addMappingRow({ transaction_type: type, amount_field: amount, enabled: 1 }); existing.add(key); added += 1; } }); updateMappingSummary(types.length, added, combinations.length); notify('已合并检索全部店铺 ' + combinations.length + ' 个非零金额组合,新增 ' + added + ' 条共用映射'); } catch (e) { notify('合并获取有金额的 Type 失败:' + e.message); } finally { if (button) button.disabled = false; } }
const rawState = { page: 1, size: 100, pages: 1 };
async function loadRawTypes() { const store = val('spRawStore'); const select = document.getElementById('spRawType'); if (!select || !store) return; const current = select.value; try { const d = await apiGet('/api/shopify/payouts/types?store_code=' + encodeURIComponent(store)); const types = [...new Set((d.rows || []).map(x => String(x || '').trim()).filter(Boolean))]; select.innerHTML = '' + types.map(x => '').join(''); if (types.includes(current)) select.value = current; } catch (e) {} }
async function renderRawTransactions() { const store = val('spRawStore'), payoutId = val('spRawPayoutId').trim(), type = val('spRawType'); let u = '/api/shopify/payouts/raw-transactions?page=' + rawState.page + '&page_size=' + rawState.size; if (store) u += '&store_code=' + encodeURIComponent(store); if (payoutId) u += '&payout_id=' + encodeURIComponent(payoutId); if (type) u += '&transaction_type=' + encodeURIComponent(type); try { const d = await apiGet(u); rawState.pages = Math.max(1, Math.ceil(Number(d.total || 0) / Number(d.page_size || rawState.size))); const tb = document.getElementById('spRawTb'); if (tb) tb.innerHTML = (d.rows || []).map(r => '| ' + escHtml(r.processed_at || '-') + ' | ' + escHtml(r.payout_id) + ' | ' + escHtml(r.transaction_id) + ' | ' + escHtml(r.transaction_type || '-') + ' | ' + escHtml(r.source_type || '-') + ' | ' + escHtml(r.adjustment_reason || '-') + ' | ' + escHtml(r.source_order_id || '-') + ' | ' + money(r.amount) + ' | ' + money(r.fee) + ' | ' + money(r.net) + ' | ' + escHtml(r.currency || '-') + ' |
').join('') || '暂无原始 API 明细,请先同步 Payout |
'; const info = document.getElementById('spRawPageInfo'); if (info) info.textContent = '第 ' + d.page + ' / ' + rawState.pages + ' 页,共 ' + d.total + ' 条'; } catch (e) { notify('加载 API 原始明细失败:' + e.message); } }
async function syncRawPayout() { const store = val('spRawStore'), payoutId = val('spRawPayoutId').trim(); if (!store || !payoutId || !/^\d+$/.test(payoutId)) return notify('请选择店铺并填写有效的 Payout ID'); try { const r = await apiPost('/api/shopify/payouts/sync', { store_codes: [store], payout_id: Number(payoutId), mode: 'sync' }); notify(r.accepted ? '已提交该 Payout 的 API 原始同步任务' : (r.message || '已有任务正在运行')); } catch (e) { notify('提交 API 同步失败:' + e.message); } }
function setupRawPage() { const main = document.querySelector('main.main') || document.querySelector('.main') || document.body; if (!document.getElementById('shopify-payout-raw')) main.insertAdjacentHTML('beforeend', rawPageHtml()); const bind = (id, ev, fn) => { const el = document.getElementById(id); if (el && !el.dataset.bound) { el.dataset.bound = '1'; el.addEventListener(ev, fn); } }; bind('spRawQuery', 'click', () => { rawState.page = 1; renderRawTransactions(); }); bind('spRawSync', 'click', syncRawPayout); bind('spRawPrev', 'click', () => { rawState.page = Math.max(1, rawState.page - 1); renderRawTransactions(); }); bind('spRawNext', 'click', () => { rawState.page = Math.min(rawState.pages, rawState.page + 1); renderRawTransactions(); }); bind('spRawPageSize', 'change', () => { rawState.size = Number(val('spRawPageSize')) || 100; rawState.page = 1; renderRawTransactions(); }); }
function valFrom(tr, key) { const x = tr.querySelector('[data-k="' + key + '"]'); return x ? x.value : ''; }
// Keep the original global handlers available for existing buttons/bookmarks.
window.addSpmRow = addMappingRow;
window.loadSpm = loadMappings;
window.saveSpm = saveMappings;
window.addSpfRow = addFieldRow;
window.saveSpf = saveFields;
window.pullShopifyKingdeeFields = pullFields;
window.refreshSpmTypes = loadTypes;
function setupPages() { addNav('shopify-payout-detail', 'Payouts 打款记录明细'); addNav('shopify-payout-raw', 'Payouts API 原始明细'); addNav('shopify-payout-mapping', '映射关系'); addNav('shopify-payout-field-map', '金蝶单据字段映射'); const main = document.querySelector('main.main') || document.querySelector('.main') || document.body; if (!document.getElementById('shopify-payout-mapping')) main.insertAdjacentHTML('beforeend', mappingPageHtml()); if (!document.getElementById('shopify-payout-field-map')) main.insertAdjacentHTML('beforeend', fieldPageHtml()); setupRawPage(); const bind = (id, ev, fn) => { const el = document.getElementById(id); if (el && !el.dataset.bound) { el.dataset.bound = '1'; el.addEventListener(ev, fn); } }; bind('spmLoad', 'click', loadMappings); bind('spmSave', 'click', saveMappings); bind('spmTypes', 'click', loadTypes); bind('spmKingdeeFields', 'click', loadMappingFieldOptions); bind('spmAdd', 'click', () => addMappingRow()); bind('spmFieldStore', 'change', () => { loadFields().catch(e => notify('切换店铺字段映射失败:' + e.message)); refreshFieldCopyTargets().catch(e => notify('加载目标店铺失败:' + e.message)); }); bind('spmCopyFields', 'click', () => copyFields().catch(e => notify('复制字段映射失败:' + e.message))); bind('spmPullFields', 'click', pullFields); bind('spmSaveFields', 'click', saveFields); bind('spmSaveFieldsBottom', 'click', saveFields); bind('spmAddField', 'click', () => addFieldRow()); installPayoutPermissions(); if (typeof window.applyNavPermissions === 'function') window.applyNavPermissions(); }
function ensurePayoutHeader() {
const table = document.querySelector('#shopifyPayoutTb')?.closest('table');
if (!table) return;
table.querySelector('thead tr').innerHTML = ' | 店铺 | 单据编号 | Payout date | 交易日期 | Status | Amount | Currency | 同步时间 | 推送状态 | 金蝶单据号 | 单据内码 | 推送信息 | 推送金蝶 | ';
const all = document.getElementById('spPayoutAll');
if (all) all.onchange = () => document.querySelectorAll('.sp-payout-row-check').forEach(x => x.checked = all.checked);
const sec = document.getElementById('shopify-payouts');
const title = sec?.querySelector('.section-title');
const syncStatus = document.getElementById('shopifyPayoutSyncStatus');
const actions = syncStatus?.parentElement || (title?.children.length > 1 ? title.lastElementChild : null);
if (actions) actions.classList.add('section-actions', 'payout-actions');
let refresh = title?.querySelector('.sp-refresh-payouts');
if (title && !refresh) {
refresh = document.createElement('button');
refresh.className = 'btn2 sp-refresh-payouts';
refresh.textContent = '刷新列表';
refresh.onclick = refreshPayoutsAndClearFailures;
}
if (refresh && actions) actions.insertBefore(refresh, syncStatus?.nextSibling || actions.firstChild);
if (title && !title.querySelector('.sp-push-header')) {
const b = document.createElement('button');
b.className = 'btn3 sp-push-header';
b.textContent = '推送金蝶';
b.onclick = pushSelectedPayouts;
(actions || title).appendChild(b);
}
}
function payoutPushStatusHtml(value) {
const status = String(value || 'pending').trim().toLowerCase();
const labels = {
pending: '待推送',
success: '推送成功',
partial: '部分推送',
failed: '推送失败',
skipped: '已跳过',
pushing: '推送中',
running: '推送中',
error: '推送失败'
};
const tone = status === 'success' ? 'success' :
(status === 'failed' || status === 'error') ? 'failed' :
status === 'partial' ? 'warning' : 'pending';
return '' +
escHtml(labels[status] || value || '待推送') + '';
}
function payoutActionHtml(r) {
const store = escHtml(r.store_code), record = Number(r.id), documentNumber = escHtml(r.document_number || r.payout_id);
const pushed = ['success', 'partial'].includes(String(r.push_status || '').toLowerCase()) || r.kingdee_bill_no || r.kingdee_id;
if (pushed) return '';
return '';
}
function payoutMessageHtml(message) {
const full = String(message || '').trim();
if (!full) return '-';
const tooltip = full.replace(/\s+/g, ' ');
return '';
}
window.copyPayoutMessage = async function (button) {
const message = button?.dataset?.message || '';
if (!message) return;
try {
if (!navigator.clipboard?.writeText) throw new Error('clipboard unavailable');
await navigator.clipboard.writeText(message);
} catch (e) {
const textarea = document.createElement('textarea');
textarea.value = message;
textarea.style.position = 'fixed';
textarea.style.opacity = '0';
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
textarea.remove();
}
notify('完整推送信息已复制');
};
function payoutRow(r) { const tx = r.transaction_date_min && r.transaction_date_max ? r.transaction_date_min + ' ~ ' + r.transaction_date_max : '源数据无 processed_at'; const documentNumber = r.document_number || r.payout_id; return ' | ' + escHtml(r.store_code) + ' | ' + shopifyPayoutDocumentLink(r) + ' | ' + escHtml(r.date || '') + ' | ' + escHtml(tx) + ' | ' + escHtml(window.shopifyPayoutStatusLabel ? shopifyPayoutStatusLabel(r.status) : r.status) + ' | ' + (window.money ? money(r.amount) : r.amount) + ' | ' + escHtml(r.currency) + ' | ' + escHtml(r.sync_at || '') + ' | ' + payoutPushStatusHtml(r.push_status) + ' | ' + escHtml(r.kingdee_bill_no || '') + ' | ' + escHtml(r.kingdee_id || '') + ' | ' + payoutMessageHtml(r.push_message) + ' | ' + payoutActionHtml(r) + ' |
'; }
window.renderShopifyPayouts = async function () { try { await ensureShopifyStores(); const f = st.shopifyPayoutFilters; let u = '/api/shopify/payouts?page=' + st.shopifyPayoutPage.page + '&page_size=' + st.shopifyPayoutPage.size; [['store_code', f.store], ['date_min', f.date_min], ['date_max', f.date_max], ['document_number', f.document_number], ['status', f.status && f.status !== 'all' ? f.status : '']].forEach(([k, v]) => { if (v) u += '&' + k + '=' + encodeURIComponent(v); }); const d = await apiGet(u); payoutPageRows = d.rows || []; ensurePayoutHeader(); document.getElementById('shopifyPayoutTb').innerHTML = payoutPageRows.map(payoutRow).join('') || '暂无数据 |
'; document.getElementById('shopifyPayoutPageInfo').innerText = '第 ' + d.page + ' / ' + Math.max(1, Math.ceil(d.total / d.page_size)) + ' 页,共 ' + d.total + ' 条'; } catch (e) { notify('加载 Payouts 失败:' + e.message); } };
async function refreshPayoutsAndClearFailures() {
const button = document.querySelector('#shopify-payouts .sp-refresh-payouts');
const ids = payoutPageRows
.filter(row => ['failed', 'error'].includes(String(row.push_status || '').toLowerCase()))
.map(row => Number(row.id))
.filter(Boolean);
if (button) button.disabled = true;
try {
let cleared = 0;
if (ids.length) {
const result = await apiPost('/api/shopify/payouts/clear-failed-push-messages', { ids });
cleared = Number(result.cleared || 0);
}
await window.renderShopifyPayouts();
notify('列表已刷新' + (cleared ? '(已清理 ' + cleared + ' 条失败信息)' : ',当前页没有失败信息'));
} catch (e) {
notify('刷新列表失败:' + e.message);
} finally {
if (button) button.disabled = false;
}
}
async function submitPayoutPush(store, record, transactionIds, force) { const body = { store_code: store, payout_record_id: Number(record), force: force ? 1 : 0 }; if (transactionIds) body.transaction_ids = transactionIds; return apiPost('/api/shopify/payouts/push', body); }
window.pushShopifyPayout = async function (store, record, btn, documentNumber) { const label = documentNumber || record; if (!confirm('确认推送 Payout ' + label + ' 到金蝶?')) return; if (btn) btn.disabled = true; try { const r = await submitPayoutPush(store, record, null, false); notify((r.ok ? '' : label + ':') + (r.message || (r.ok ? '推送成功' : '推送失败'))); await window.renderShopifyPayouts(); } catch (e) { notify(label + ':' + e.message); } finally { if (btn) btn.disabled = false; } };
window.checkShopifyPayoutKingdee = async function (store, record, btn, documentNumber) { const label = documentNumber || record; if (btn) btn.disabled = true; try { const r = await apiPost('/api/shopify/payouts/push/' + Number(record) + '/check?store_code=' + encodeURIComponent(store), {}); notify(label + ':' + (r.message || (r.ok ? '检查完成' : '检查失败'))); await window.renderShopifyPayouts(); } catch (e) { notify(label + ':' + e.message); } finally { if (btn) btn.disabled = false; } };
async function pushSelectedPayouts() { const rows = [...document.querySelectorAll('.sp-payout-row-check:checked')]; if (!rows.length) return notify('请先勾选要推送的 Payout'); if (!confirm('确认将选中的 ' + rows.length + ' 条 Payout 推送到金蝶?')) return; const button = document.querySelector('#shopify-payouts .sp-push-header'); if (button) button.disabled = true; const results = []; try { for (const x of rows) { try { const r = await submitPayoutPush(x.dataset.store, x.dataset.record, null, false); results.push({ document: x.dataset.document || x.dataset.record, ok: !!r.ok, message: r.message || (r.ok ? '推送成功' : '推送失败') }); } catch (e) { results.push({ document: x.dataset.document || x.dataset.record, ok: false, message: e.message }); } } const success = results.filter(x => x.ok).length, failed = results.filter(x => !x.ok); notify('选中 ' + rows.length + ' 条,推送成功 ' + success + ' 条,未推送 ' + failed.length + ' 条' + (failed.length ? ';' + failed.map(x => x.document + ':' + x.message).join(';') : '')); await window.renderShopifyPayouts(); } finally { if (button) button.disabled = false; } }
window.renderShopifyPayoutDetail = async function () {
try {
const d = st.shopifyPayoutDetail;
await refreshPayoutTypeFilter(d.store_code);
let u = '/api/shopify/payouts/' + d.payout_id + '/transactions?page=' + d.page + '&page_size=' + d.size + '&store_code=' + encodeURIComponent(d.store_code) + '&payout_record_id=' + d.payout_record_id;
if (d.type) u += '&transaction_type=' + encodeURIComponent(d.type);
const data = await apiGet(u);
const table = document.querySelector('#shopifyPayoutDetailTb')?.closest('table');
if (table) table.querySelector('thead tr').innerHTML = ' | Date | Order | 原始 API Type | 系统类型 | Payment method | Amount | Fee | Net | Currency | 映射状态(原始 Type+金额类型) | ';
document.getElementById('shopifyPayoutDetailTb').innerHTML = (data.rows || []).map(r => { const details = r.mapping_details || {}; const mapText = Object.keys(details).map(k => escHtml(k) + ':' + escHtml(details[k].status) + (details[k].message ? '(' + escHtml(details[k].message) + ')' : '')).join('
') || '未配置'; const displayType = shopifyTxTypeLabel(r); const rawType = String(r.transaction_type || ''); return ' | ' + escHtml(shopifyDateOnly(r.processed_at)) + ' | ' + shopifyOrderDisplay(r) + ' | ' + escHtml(rawType || '-') + ' | ' + escHtml(displayType || '-') + ' | ' + escHtml(r.payment_method || '-') + ' | ' + money(r.amount) + ' | ' + money(-Number(r.fee || 0)) + ' | ' + money(r.net) + ' | ' + escHtml(r.currency || '') + ' | ' + mapText + ' |
'; }).join('') || '暂无明细数据 |
';
const all = document.getElementById('spTxAll');
if (all) all.onchange = () => document.querySelectorAll('.spTxCheck').forEach(x => x.checked = all.checked);
const sec = document.getElementById('shopify-payout-detail'), title = sec?.querySelector('.section-title'), actions = title?.children.length > 1 ? title.lastElementChild : null;
if (title && !title.querySelector('.sp-push-selected')) {
const b = document.createElement('button');
b.className = 'btn3 sp-push-selected';
b.textContent = '推送选中明细到金蝶';
b.onclick = async () => {
const current = st.shopifyPayoutDetail;
const ids = [...document.querySelectorAll('.spTxCheck:checked')].map(x => Number(x.dataset.id)).filter(Boolean);
if (!ids.length) return notify('请至少选择一条明细');
if (!confirm('确认只推送当前勾选的 ' + ids.length + ' 条 Payout 明细到金蝶?')) return;
b.disabled = true;
try {
const r = await submitPayoutPush(current.store_code, current.payout_record_id, ids, false);
notify(r.message || (r.ok ? '明细推送成功' : '明细未推送'));
if (r.ok) {
await window.renderShopifyPayoutDetail();
await window.renderShopifyPayouts();
}
} catch (e) {
notify('明细推送失败:' + e.message);
} finally {
b.disabled = false;
}
};
if (actions) actions.insertBefore(b, actions.firstElementChild); else title.appendChild(b);
}
if (window.renderShopifyPayoutTotals) renderShopifyPayoutTotals(data);
document.getElementById('shopifyPayoutDetailPageInfo').innerText = '第 ' + data.page + ' / ' + Math.max(1, Math.ceil(data.total / data.page_size)) + ' 页,共 ' + data.total + ' 条';
} catch (e) {
notify('加载明细失败:' + e.message);
}
};
const originalShow = window.showSection;
window.showSection = function (id) { setupPages(); if (id === 'shopify-payout-mapping' || id === 'shopify-payout-field-map' || id === 'shopify-payout-detail' || id === 'shopify-payout-raw') { document.querySelectorAll('.section').forEach(x => x.classList.remove('active')); const sec = document.getElementById(id); if (sec) sec.classList.add('active'); document.querySelectorAll('[data-s]').forEach(x => x.classList.toggle('active', x.dataset.s === id)); const title = document.getElementById('title'), desc = document.getElementById('desc'); if (title) title.innerText = id === 'shopify-payout-mapping' ? 'Payouts 映射关系' : id === 'shopify-payout-field-map' ? '金蝶单据字段映射' : id === 'shopify-payout-raw' ? 'Payouts API 原始明细' : 'Payouts 打款记录明细'; if (desc) desc.innerText = 'Shopify Payouts 数据管理'; if (id === 'shopify-payout-raw') populateStoreOptions(['spRawStore']).catch(() => {}); if (id === 'shopify-payout-field-map') openFieldMappingPage(); return; } if (typeof originalShow === 'function') originalShow(id); };
document.addEventListener('DOMContentLoaded', setupPages);
})();