HEL-123: 第4批——公司端剩余6页 + 对应弹窗换原型壳

- upload 流水导入: 多步流程(选文件→解析→分sheet审核→确认)完整保留, 内联卡片化 + 批次详情弹窗 #modal-batch
- manual 手工记录: 两栏布局 + 撤回确认弹窗 #withdraw-modal
- flows 流水管理: 公司端数据集 + 待确认/已确认状态口径 + 详情弹窗
- reconcile 往来确认: 候选匹配/匹配度/断档提示 + 未选候选禁用态 + 单边全部匹配空态
- accounts 银行账户: 表格 + 账户明细弹窗 + 新账户登记弹窗
- notifications 通知: tabs/已读/空态/角标
- 跨批依赖: 往来确认完成后回写工作台待办区(#workspacePendingStatus/待办行/账期流程条)

Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
leefer
2026-08-25 11:03:17 +08:00
co-authored by multica-agent
parent 307f9ba76f
commit 2fe06b74ae
2 changed files with 1209 additions and 194 deletions
+500 -142
View File
@@ -667,38 +667,65 @@ function initPairQueries() {
$("#traceModal")?.addEventListener("click", (event) => { if (event.target === event.currentTarget) event.currentTarget.classList.remove("open"); });
}
function manualStatusMeta(status) {
if (status === "已确认") return { cls: "pill-success", label: "已通过" };
if (status === "已驳回") return { cls: "pill-danger", label: "已驳回" };
return { cls: "pill-info", label: "待审核" };
}
function renderCompanyManualRecords() {
const tbody = $("#manualRecordRows");
if (!tbody) return;
$$('[data-stored-record]', tbody).forEach((row) => row.remove());
tbody.replaceChildren();
const records = readStoredRecords(storageKeys.manual).filter((record) => record.company === "A公司");
[...records].reverse().forEach((record) => {
const row = document.createElement("tr");
row.dataset.storedRecord = record.id;
const identity = document.createElement("td");
const id = document.createElement("strong"); id.textContent = record.id;
const date = document.createElement("small"); date.textContent = record.transactionDate;
identity.append(id, date);
const direction = document.createElement("td");
const directionName = document.createElement("strong"); directionName.textContent = record.direction;
const subject = document.createElement("small"); subject.textContent = record.subject;
direction.append(directionName, subject);
const source = document.createElement("td"); source.textContent = record.sourceAccount;
const date = document.createElement("td"); date.className = "num"; date.textContent = record.transactionDate || "—";
const direction = document.createElement("td"); direction.textContent = record.direction || "—";
const counterparty = document.createElement("td");
const counterpartyName = document.createElement("strong"); counterpartyName.textContent = record.counterparty;
const counterpartyType = document.createElement("small"); counterpartyType.textContent = record.counterpartyType;
const counterpartyName = document.createElement("span"); counterpartyName.className = "cell-main"; counterpartyName.textContent = record.counterparty;
const counterpartyType = document.createElement("span"); counterpartyType.className = "cell-sub"; counterpartyType.textContent = record.counterpartyType || "";
counterparty.append(counterpartyName, counterpartyType);
const summary = document.createElement("td"); summary.textContent = record.summary;
const amount = document.createElement("td"); amount.className = "number"; amount.textContent = formatCurrency(record.amount);
const subject = document.createElement("td"); subject.innerHTML = `<span class="tag">${record.subject || "—"}</span>`;
const isIn = record.direction === "收款";
const amount = document.createElement("td");
amount.className = `num-col ${isIn ? "amt-in" : "amt-out"}`;
amount.textContent = `${isIn ? "+" : "-"}¥ ${formatCurrency(record.amount)}`;
const summary = document.createElement("td"); summary.className = "wrap"; summary.textContent = record.summary || "—";
const statusMeta = manualStatusMeta(record.status);
const statusCell = document.createElement("td");
const status = recordStatus(record.status);
const badge = document.createElement("span"); badge.className = `status ${status.className}`; badge.textContent = status.label;
statusCell.append(badge);
row.append(identity, direction, source, counterparty, summary, amount, statusCell);
statusCell.innerHTML = `<span class="pill ${statusMeta.cls}">${statusMeta.label}</span>`;
const action = document.createElement("td");
if (record.status === "待总账复核") {
action.innerHTML = '<button type="button" class="btn btn-sm btn-danger" data-action="withdraw">撤回</button>';
} else {
action.innerHTML = '<span class="muted">—</span>';
}
row.append(date, direction, counterparty, subject, amount, summary, statusCell, action);
tbody.append(row);
});
const pending = records.filter((record) => record.status === "待总账复核").length + 1;
if ($("#manualPendingStatus")) $("#manualPendingStatus").textContent = `${pending} 笔待总账复核`;
updateManualCounts();
}
function updateManualCounts() {
const records = readStoredRecords(storageKeys.manual).filter((record) => record.company === "A公司");
const pending = records.filter((record) => record.status === "待总账复核").length;
const pendingEl = $("#manualPendingStatus");
if (pendingEl) pendingEl.textContent = pending;
const foot = $("#manualFoot");
if (foot) foot.textContent = `${records.length} 条 · 待复核 ${pending}`;
const empty = $("#manualEmpty");
if (empty) empty.hidden = records.length > 0;
}
function fillAccountSelects(accounts) {
@@ -716,38 +743,49 @@ function fillAccountSelects(accounts) {
});
}
function companyAccountMeta(status) {
if (status === "active") return { status: { cls: "pill-success", label: "启用" }, audit: { cls: "pill-success", label: "已审核" } };
if (status === "returned") return { status: { cls: "pill-danger", label: "已退回" }, audit: { cls: "pill-danger", label: "已退回" } };
if (status === "disabled") return { status: { cls: "pill-muted", label: "停用" }, audit: { cls: "pill-success", label: "已审核" } };
return { status: { cls: "pill-info", label: "待启用" }, audit: { cls: "pill-info", label: "待审核" } };
}
function renderCompanyAccounts(accounts) {
const directory = $("#accountDirectory");
if (directory) {
directory.replaceChildren(...accounts.map((account) => {
const article = document.createElement("article");
const header = document.createElement("header");
const mark = document.createElement("span"); mark.className = "bank-mark"; mark.textContent = account.bank_name.slice(0, 1);
const identity = document.createElement("div");
const name = document.createElement("strong"); name.textContent = account.bank_name;
const meta = document.createElement("small"); meta.textContent = `${account.account_type} · 尾号 ${accountTail(account.account_number_masked)}`;
identity.append(name, meta);
const status = recordStatus(accountStatusLabel(account.status));
const badge = document.createElement("em"); badge.className = `status ${status.className}`; badge.textContent = status.label;
header.append(mark, identity, badge);
const details = document.createElement("dl");
const rows = [
["申请启用", account.effective_from || "待审核确定"],
["流水覆盖", account.usable ? "尚未上传" : "不参与计算"],
["提交时间", String(account.created_at || "").slice(0, 10) || "—"],
];
if (account.status === "returned" && account.review_reason) rows.push(["退回原因", account.review_reason]);
if (account.status === "disabled" && account.effective_to) rows.push(["停用日期", account.effective_to]);
rows.forEach(([term, value]) => {
const wrapper = document.createElement("div");
const dt = document.createElement("dt"); dt.textContent = term;
const dd = document.createElement("dd"); dd.textContent = value;
wrapper.append(dt, dd); details.append(wrapper);
});
article.append(header, details);
return article;
state.accounts = accounts;
const tbody = $("#account-tbody");
if (tbody) {
tbody.replaceChildren(...accounts.map((account) => {
const row = document.createElement("tr");
row.dataset.accountId = account.id;
const identity = document.createElement("td");
const name = document.createElement("span"); name.className = "cell-main"; name.textContent = account.bank_name;
const tail = document.createElement("span"); tail.className = "cell-sub"; tail.textContent = `尾号 ${accountTail(account.account_number_masked)}`;
identity.append(name, tail);
const type = document.createElement("td"); type.innerHTML = `<span class="tag">${account.account_type || "—"}</span>`;
const meta = companyAccountMeta(account.status);
const statusCell = document.createElement("td"); statusCell.innerHTML = `<span class="pill ${meta.status.cls}">${meta.status.label}</span>`;
const coverage = document.createElement("td");
coverage.className = "num muted";
coverage.textContent = account.usable ? "尚未上传" : "—";
const auditCell = document.createElement("td"); auditCell.innerHTML = `<span class="pill ${meta.audit.cls}">${meta.audit.label}</span>`;
const action = document.createElement("td"); action.innerHTML = '<button type="button" class="btn btn-sm" data-account-view>查看明细</button>';
row.append(identity, type, statusCell, coverage, auditCell, action);
return row;
}));
}
const count = $("#account-count");
if (count) count.textContent = accounts.length;
const foot = $("#account-foot");
if (foot) foot.textContent = `${accounts.length} 个账户`;
const empty = $("#account-empty");
if (empty) empty.hidden = accounts.length > 0;
fillAccountSelects(accounts);
}
@@ -1406,13 +1444,34 @@ const FLOW_DEMO = [
{ date: "2026-07-25", company: "金牛贸易", bank: "农行", account: "2208", acctLabel: "农行 · 尾号 2208", dir: "付", dirPill: "pill-danger", peer: "安阳钢铁集团有限责任公司", summary: "螺纹钢采购款(7 月)", serial: "ABC202607250819673", status: "未归集", statusPill: "pill-muted", amount: "5,620,000.00", amtClass: "amt-out", detail: { bank: "农业银行", account: "河南金牛贸易有限公司 · 基本户 1606 3301 0400 0220 8", time: "2026-07-25 10:19:05", peer: "安阳钢铁集团有限责任公司", peerAcct: "工行安阳分行 1706 0211 0900 4428 17", amount: "¥ 5,620,000.00(付)", status: "未归集", pair: "—(外部单位交易)", subject: "—", batch: "—", note: "对外螺纹钢采购付款,不参与集团内部往来归集。" } },
];
const COMPANY_FLOWS = [
{ date: "2026-07-02", company: "金牛煤业", bank: "工商银行", account: "3305", acctLabel: "工行 · 尾号 3305", dir: "收", dirPill: "pill-success", peer: "河南金牛置业有限公司", summary: "煤炭采购款(2026 年 6 月供煤合同结算)", serial: "ICBC2026070200185347", status: "已归集", statusPill: "pill-success", amount: "3,200,000.00", amtClass: "amt-in", detail: { bank: "工商银行", account: "河南金牛煤业有限公司 · 基本户 1702 0231 0900 8133 05", time: "2026-07-02 15:47:22", peer: "河南金牛置业有限公司", peerAcct: "中行郑州郑东新区支行 2546 0387 0200 8821", amount: "¥ 3,200,000.00(收)", status: "已归集", pair: "金牛煤业 ↔ 金牛置业", subject: "应收(煤业侧)", batch: "JH-202607-014", note: "与置业侧中行尾号 8821 账户付款流水已双向匹配。" } },
{ date: "2026-07-03", company: "金牛煤业", bank: "工商银行", account: "3305", acctLabel: "工行 · 尾号 3305", dir: "付", dirPill: "pill-danger", peer: "山西晋城王坡煤矿有限责任公司", summary: "原料煤采购预付款", serial: "ICBC2026070300221091", status: "未归集 · 外部", statusPill: "pill-muted", amount: "860,000.00", amtClass: "amt-out", detail: { bank: "工商银行", account: "河南金牛煤业有限公司 · 基本户 1702 0231 0900 8133 05", time: "2026-07-03 10:26:31", peer: "山西晋城王坡煤矿有限责任公司", peerAcct: "工行晋城分行 1702 0218 0902 6641 20", amount: "¥ 860,000.00(付)", status: "未归集 · 外部", pair: "—(外部单位交易)", subject: "—", batch: "—", note: "对方为集团外供应商,不参与内部往来归集,仅作银行流水留档。" } },
{ date: "2026-07-06", company: "金牛煤业", bank: "中国银行", account: "9916", acctLabel: "中行 · 尾号 9916", dir: "收", dirPill: "pill-success", peer: "河南神火运销有限公司", summary: "动力煤销售货款(7 月第一批)", serial: "BOC2026070600772018", status: "未归集 · 外部", statusPill: "pill-muted", amount: "1,246,800.00", amtClass: "amt-in", detail: { bank: "中国银行", account: "河南金牛煤业有限公司 · 一般户 2546 0387 1100 9916", time: "2026-07-06 09:58:44", peer: "河南神火运销有限公司", peerAcct: "工行永城分行 1702 0218 0902 9075 63", amount: "¥ 1,246,800.00(收)", status: "未归集 · 外部", pair: "—(外部单位交易)", subject: "—", batch: "—", note: "对外动力煤销售回款,不参与集团内部往来归集。" } },
{ date: "2026-07-08", company: "金牛煤业", bank: "工商银行", account: "3305", acctLabel: "工行 · 尾号 3305", dir: "付", dirPill: "pill-danger", peer: "河南金牛物流有限公司", summary: "6 月煤炭公路运输费结算", serial: "ICBC2026070800311276", status: "已归集", statusPill: "pill-success", amount: "486,500.00", amtClass: "amt-out", detail: { bank: "工商银行", account: "河南金牛煤业有限公司 · 基本户 1702 0231 0900 8133 05", time: "2026-07-08 11:15:09", peer: "河南金牛物流有限公司", peerAcct: "建行郑州经开区支行 4105 0167 8080 5562", amount: "¥ 486,500.00(付)", status: "已归集", pair: "金牛煤业 ↔ 金牛物流", subject: "应付(煤业侧)", batch: "JH-202607-014", note: "与物流侧建行收款流水已双向匹配,运单 42 张随附。" } },
{ date: "2026-07-10", company: "金牛煤业", bank: "中国银行", account: "9916", acctLabel: "中行 · 尾号 9916", dir: "付", dirPill: "pill-danger", peer: "河南金牛贸易有限公司", summary: "选煤设备配件代购款", serial: "BOC2026071000819455", status: "待确认", statusPill: "pill-warn", amount: "214,700.00", amtClass: "amt-out", detail: { bank: "中国银行", account: "河南金牛煤业有限公司 · 一般户 2546 0387 1100 9916", time: "2026-07-10 14:08:52", peer: "河南金牛贸易有限公司", peerAcct: "农行郑州金水支行 1606 3301 0400 0220 8", amount: "¥ 214,700.00(付)", status: "待确认", pair: "金牛煤业 ↔ 金牛贸易", subject: "其他应付(待复核)", batch: "JH-202607-021", note: "贸易侧已确认收款,科目待双方复核(应付 / 其他应付)。" } },
{ date: "2026-07-13", company: "金牛煤业", bank: "工商银行", account: "3305", acctLabel: "工行 · 尾号 3305", dir: "付", dirPill: "pill-danger", peer: "国网河南省电力公司新密市供电公司", summary: "7 月工业电费", serial: "ICBC2026071300458820", status: "未归集 · 外部", statusPill: "pill-muted", amount: "1,528,300.00", amtClass: "amt-out", detail: { bank: "工商银行", account: "河南金牛煤业有限公司 · 基本户 1702 0231 0900 8133 05", time: "2026-07-13 10:19:05", peer: "国网河南省电力公司新密市供电公司", peerAcct: "工行新密支行 1706 0211 0900 4428 17", amount: "¥ 1,528,300.00(付)", status: "未归集 · 外部", pair: "—(外部单位交易)", subject: "—", batch: "—", note: "对外电费支出,不参与集团内部往来归集。" } },
{ date: "2026-07-15", company: "金牛煤业", bank: "中国银行", account: "9916", acctLabel: "中行 · 尾号 9916", dir: "收", dirPill: "pill-success", peer: "河南金牛新能源有限公司", summary: "场区租赁费返还(二季度)", serial: "BOC2026071500923314", status: "单边", statusPill: "pill-danger", amount: "95,000.00", amtClass: "amt-in", detail: { bank: "中国银行", account: "河南金牛煤业有限公司 · 一般户 2546 0387 1100 9916", time: "2026-07-15 11:06:33", peer: "河南金牛新能源有限公司", peerAcct: "工行郑州分行 1704 0512 0900 2266 49", amount: "¥ 95,000.00(收)", status: "单边", pair: "金牛煤业 ↔ 金牛新能源", subject: "其他应收(煤业侧)", batch: "—(待归集)", note: "新能源侧尚未提报对应付款流水,形成单边。" } },
{ date: "2026-07-18", company: "金牛煤业", bank: "工商银行", account: "3305", acctLabel: "工行 · 尾号 3305", dir: "付", dirPill: "pill-danger", peer: "国家税务总局新密市税务局", summary: "增值税及附加税费(6 月属期)", serial: "ICBC2026071800506639", status: "未归集 · 外部", statusPill: "pill-muted", amount: "2,073,450.00", amtClass: "amt-out", detail: { bank: "工商银行", account: "河南金牛煤业有限公司 · 基本户 1702 0231 0900 8133 05", time: "2026-07-18 13:29:40", peer: "国家税务总局新密市税务局", peerAcct: "国库专户", amount: "¥ 2,073,450.00(付)", status: "未归集 · 外部", pair: "—(外部单位交易)", subject: "—", batch: "—", note: "税费缴库,不参与集团内部往来归集。" } },
{ date: "2026-07-21", company: "金牛煤业", bank: "中国银行", account: "9916", acctLabel: "中行 · 尾号 9916", dir: "收", dirPill: "pill-success", peer: "永城煤电控股集团有限公司", summary: "块煤销售款(年度长协第 7 批)", serial: "BOC2026072101054472", status: "未归集 · 外部", statusPill: "pill-muted", amount: "3,864,000.00", amtClass: "amt-in", detail: { bank: "中国银行", account: "河南金牛煤业有限公司 · 一般户 2546 0387 1100 9916", time: "2026-07-21 09:35:12", peer: "永城煤电控股集团有限公司", peerAcct: "工行永城分行 1702 0218 0902 3477 12", amount: "¥ 3,864,000.00(收)", status: "未归集 · 外部", pair: "—(外部单位交易)", subject: "—", batch: "—", note: "对外块煤销售回款,不参与集团内部往来归集。" } },
{ date: "2026-07-24", company: "金牛煤业", bank: "交通银行", account: "7710", acctLabel: "交行 · 尾号 7710", dir: "收", dirPill: "pill-success", peer: "河南金牛置业有限公司", summary: "临时往来款归还", serial: "BCM2026072400088116", status: "待确认", statusPill: "pill-warn", amount: "1,500,000.00", amtClass: "amt-in", detail: { bank: "交通银行", account: "河南金牛煤业有限公司 · 一般户 4110 6120 0181 0077 10(账户待审核)", time: "2026-07-24 14:52:26", peer: "河南金牛置业有限公司", peerAcct: "中行郑州郑东新区支行 2546 0387 0200 8821", amount: "¥ 1,500,000.00(收)", status: "待确认", pair: "金牛煤业 ↔ 金牛置业", subject: "其他应收(煤业侧)", batch: "JH-202607-021", note: "交行 7710 账户尚处待审核,归集结果以账户审核通过后为准。" } },
{ date: "2026-07-27", company: "金牛煤业", bank: "工商银行", account: "3305", acctLabel: "工行 · 尾号 3305", dir: "付", dirPill: "pill-danger", peer: "煤业职工工资代发(2026 年 7 月)", summary: "7 月职工工资及奖金代发,共 612 人", serial: "ICBC2026072700582241", status: "未归集 · 外部", statusPill: "pill-muted", amount: "2,416,780.00", amtClass: "amt-out", detail: { bank: "工商银行", account: "河南金牛煤业有限公司 · 基本户 1702 0231 0900 8133 05", time: "2026-07-27 10:11:57", peer: "煤业职工工资代发(2026 年 7 月)", peerAcct: "代发专户", amount: "¥ 2,416,780.00(付)", status: "未归集 · 外部", pair: "—(外部单位交易)", subject: "—", batch: "—", note: "工资代发,不参与集团内部往来归集。" } },
{ date: "2026-07-30", company: "金牛煤业", bank: "中国银行", account: "9916", acctLabel: "中行 · 尾号 9916", dir: "付", dirPill: "pill-danger", peer: "河南金牛农业科技发展有限公司", summary: "临时周转借款(约定 8 月归还)", serial: "BOC2026073001187903", status: "单边", statusPill: "pill-danger", amount: "800,000.00", amtClass: "amt-out", detail: { bank: "中国银行", account: "河南金牛煤业有限公司 · 一般户 2546 0387 1100 9916", time: "2026-07-30 16:32:08", peer: "河南金牛农业科技发展有限公司", peerAcct: "农行郑州金水支行 1606 3301 0400 0220 8", amount: "¥ 800,000.00(付)", status: "单边", pair: "金牛煤业 ↔ 金牛农业", subject: "其他应收(煤业侧)", batch: "—(待归集)", note: "农业 7 月未提交流水,暂无对方侧证据。" } },
];
function currentFlowData() {
return portal === "admin" ? FLOW_DEMO : COMPANY_FLOWS;
}
function renderFlowRows() {
const tbody = $("#flowTable tbody");
if (!tbody) return;
tbody.innerHTML = FLOW_DEMO.map((row, index) => {
return `<tr class="clickable" data-flow-idx="${index}" data-company="${row.company}" data-bank="${row.bank}" data-account="${row.account}" data-date="${row.date}">
const data = currentFlowData();
tbody.innerHTML = data.map((row, index) => {
const companyCell = portal === "admin" ? `<td class="cell-main">${row.company}</td>` : "";
return `<tr class="clickable" data-flow-idx="${index}" data-company="${row.company}" data-bank="${row.bank}" data-account="${row.account}" data-date="${row.date}" data-dir="${row.dir}">
<td class="num">${row.date}</td>
<td class="cell-main">${row.company}</td>
${companyCell}
<td>${row.acctLabel}</td>
<td><span class="pill ${row.dirPill}">${row.dir}</span></td>
<td class="wrap">${row.peer}</td>
@@ -1423,13 +1482,21 @@ function renderFlowRows() {
</tr>`;
}).join("");
const count = $("#flowCount");
if (count) count.textContent = `${FLOW_DEMO.length} · 本页 1-${FLOW_DEMO.length}`;
if (count) count.textContent = `${data.length}`;
const empty = $("#flowEmpty");
if (empty) empty.hidden = data.length > 0;
const sum = $("#flowSum");
if (sum) {
let inflow = 0, outflow = 0;
data.forEach((row) => { if (row.dir === "收") inflow += Number(row.amount.replace(/,/g, "")); else outflow += Number(row.amount.replace(/,/g, "")); });
sum.innerHTML = data.length ? `收 <span class="amt-in">+¥ ${formatCurrency(inflow)}</span> · 付 <span class="amt-out">-¥ ${formatCurrency(outflow)}</span>` : "";
}
}
function openFlowDetail(index) {
const modal = $("#tx-modal");
if (!modal) return;
const row = FLOW_DEMO[index];
const row = currentFlowData()[index];
if (!row) return;
$("#tx-modal-sub").textContent = `${row.company} · ${row.acctLabel} · ${row.date}`;
const d = row.detail;
@@ -1477,6 +1544,18 @@ function filterFlows() {
if (!row.hidden) count += 1;
});
$("#flowCount").textContent = count === 0 ? "共 0 条 · 无匹配记录" : `${count} 笔 · 本页 1-${count}`;
const empty = $("#flowEmpty");
if (empty) empty.hidden = count > 0;
const sum = $("#flowSum");
if (sum) {
let inflow = 0, outflow = 0;
$$("tbody tr", table).forEach((row) => {
if (row.hidden) return;
const amount = Number(($("td:last-child", row)?.textContent || "").replace(/[^\d.]/g, ""));
if (row.dataset.dir === "收") inflow += amount; else outflow += amount;
});
sum.innerHTML = count ? `收 <span class="amt-in">+¥ ${formatCurrency(inflow)}</span> · 付 <span class="amt-out">-¥ ${formatCurrency(outflow)}</span>` : "";
}
showToast("查询完成", `当前显示 ${count} 笔流水`);
}
@@ -1488,13 +1567,14 @@ function exportFlows() {
const cells = $$('td', row).map((cell) => cell.innerText.replace(/\n/g, " ").trim());
const imp = `IMP-DEMO-${String(index + 1).padStart(3, "0")}`;
const loc = `Sheet1!R${index + 8}`;
const companyName = state.me?.company_name || "本公司";
if (portal === "admin") return [...cells.slice(0, 9), imp, loc];
return [cells[0], "A公司", cells[1], ...cells.slice(2), imp, loc];
return [cells[0], companyName, cells[1], ...cells.slice(2), imp, loc];
});
const csv = [headers, ...records].map((record) => record.map((value) => `"${String(value ?? "").replace(/"/g, '""')}"`).join(",")).join("\r\n");
const link = document.createElement("a");
link.href = URL.createObjectURL(new Blob(["\ufeff", csv], { type: "text/csv;charset=utf-8" }));
link.download = `${portal === "admin" ? "集团" : "A公司"}银行流水_202607.csv`;
link.download = `${portal === "admin" ? "集团" : (state.me?.company_name || "公司")}银行流水_202607.csv`;
link.click();
URL.revokeObjectURL(link.href);
showToast("导出已生成", `${rows.length} 笔,已保留银行标识与源行定位`);
@@ -1503,14 +1583,14 @@ function exportFlows() {
function initFlowTools() {
$("#applyFlowFilters")?.addEventListener("click", filterFlows);
$("#exportFlows")?.addEventListener("click", exportFlows);
if (portal !== "admin") return;
renderFlowRows();
$("#resetFlowFilters")?.addEventListener("click", () => {
$("#flowCompany").value = "全部公司";
const company = $("#flowCompany");
if (company) company.value = "全部公司";
$("#flowBank").value = "全部银行";
$("#flowAccount").value = "全部账户";
$("#flowStart").value = "2026-07-01";
$("#flowEnd").value = "2026-07-31";
$("#flowEnd").value = portal === "admin" ? "2026-07-31" : "2026-08-20";
$("#flowKeyword").value = "";
filterFlows();
});
@@ -1592,8 +1672,10 @@ function renderParseResult(result, parsed) {
const sheets = Array.isArray(result.sheets) ? result.sheets : [];
const duplicated = result.status === "duplicate";
const opaque = duplicated && !sheets.length;
panel.classList.toggle("is-exception", !parsed);
$("use", panel).setAttribute("href", parsed ? "icons.svg#circle-check" : "icons.svg#circle-alert");
panel.classList.toggle("info", parsed);
panel.classList.toggle("warn", !parsed);
const parseIcon = $("use", panel);
if (parseIcon) parseIcon.setAttribute("href", parsed ? "icons.svg#circle-check" : "icons.svg#circle-alert");
$("#parseTitle").textContent = duplicated ? "文件已导入过" : parsed ? "文件解析完成" : "未识别到银行模板";
const pendingCount = sheets.filter((s) => s.outcome === "parsed" && s.review_status === "pending").length;
const exceptionCount = sheets.filter((s) => s.outcome === "exception").length;
@@ -1643,20 +1725,17 @@ function renderSheetList(sheets, batchId) {
}
function buildSheetItem(sheet, batchId) {
const item = document.createElement("article");
item.className = "sheet-item";
if (sheet.review_status === "pending") item.classList.add("is-pending");
const item = document.createElement("div");
item.className = "list-row";
const meta = sheetStatusMeta(sheet);
const head = document.createElement("div");
head.className = "sheet-item-head";
const name = document.createElement("strong");
name.textContent = sheet.sheet_name;
const badge = document.createElement("em");
badge.className = `status ${meta.className}`;
badge.textContent = meta.label;
head.append(name, badge);
const details = document.createElement("p");
details.className = "sheet-item-meta";
const main = document.createElement("div");
main.className = "lr-main";
const title = document.createElement("div");
title.className = "lr-title";
title.textContent = sheet.sheet_name;
main.append(title);
const details = document.createElement("div");
details.className = "lr-sub";
if (sheet.outcome === "parsed" && sheet.bank) {
const period = sheet.period_start ? ` · ${sheet.period_start}${sheet.period_end}` : "";
details.textContent = `${sheet.bank} · ${sheet.transactions} 条明细${period}`;
@@ -1668,28 +1747,34 @@ function buildSheetItem(sheet, batchId) {
if (sheet.review_reason) {
details.textContent += ` · 原因:${sheet.review_reason}`;
}
const body = document.createElement("div");
body.append(head, details);
main.append(details);
const badge = document.createElement("span");
badge.className = `pill ${pillClass(meta.className)}`;
badge.textContent = meta.label;
item.append(main, badge);
const actions = document.createElement("div");
actions.className = "sheet-item-actions";
if (sheet.review_status === "pending") {
const actions = document.createElement("div");
actions.className = "lr-side";
actions.style.cssText = "display:flex;gap:6px;flex:none;";
if (sheet.outcome === "parsed") {
const confirmButton = document.createElement("button");
confirmButton.type = "button";
confirmButton.className = "text-button";
confirmButton.className = "btn btn-sm btn-primary";
confirmButton.textContent = "确认";
confirmButton.addEventListener("click", () => sheetReviewAction(batchId, sheet.sheet_name, "confirm"));
actions.append(confirmButton);
}
const ignoreButton = document.createElement("button");
ignoreButton.type = "button";
ignoreButton.className = "text-button";
ignoreButton.className = "btn btn-sm";
ignoreButton.textContent = "忽略";
ignoreButton.addEventListener("click", () => sheetReviewAction(batchId, sheet.sheet_name, "ignore"));
actions.append(ignoreButton);
item.append(actions);
}
item.append(body, actions);
return item;
}
@@ -1743,7 +1828,7 @@ async function confirmImport() {
.filter((s) => s.outcome === "parsed" && s.review_status === "pending")
.map((s) => s.sheet_name);
if (!pending.length) {
$("#uploadDialog").close();
resetUpload();
await loadImportBatches();
showView("upload");
return;
@@ -1770,37 +1855,37 @@ async function confirmImport() {
button.disabled = false;
if (Array.isArray(outcome.sheets)) renderSheetList(outcome.sheets, batchId);
await loadImportBatches();
$("#uploadDialog").close();
resetUpload();
showView("upload");
showToast("流水已确认", `${outcome.updated.length} 个工作表已确认;未确认的工作表不参与计算`);
}
function submitImportException() {
$("#uploadDialog").close();
resetUpload();
showView("upload");
showToast("解析异常未入账", `${state.selectedFile?.name || "该文件"} 不会进入匹配与计算,请核对模板后重新导出`);
}
function renderBatchRow(batch) {
const row = document.createElement("tr");
row.dataset.batchId = batch.id;
const idCell = document.createElement("td");
const id = document.createElement("strong");
id.textContent = `IMP-${String(batch.id).padStart(6, "0")}`;
const file = document.createElement("small");
file.textContent = batch.original_filename || "";
const id = document.createElement("span"); id.className = "cell-main num"; id.textContent = `IMP-${String(batch.id).padStart(6, "0")}`;
const file = document.createElement("span"); file.className = "cell-sub"; file.textContent = batch.original_filename || "";
idCell.append(id, file);
const bank = document.createElement("td");
bank.textContent = batch.bank_name || "—";
const period = document.createElement("td");
period.className = "num";
period.textContent = batch.period_start && batch.period_end
? `${batch.period_start}${batch.period_end}`
? `${batch.period_start} ~ ${batch.period_end}`
: "—";
const count = document.createElement("td");
count.className = "number";
count.textContent = `${batch.confirmed_transactions ?? 0}`;
count.className = "num-col";
count.textContent = `${batch.confirmed_transactions ?? 0}`;
const coverage = document.createElement("td");
const coverageStatus = batch.status === "exception"
@@ -1810,18 +1895,26 @@ function renderBatchRow(batch) {
: batch.confirmed_sheets > 0
? { className: "success", label: "已确认" }
: { className: "neutral", label: "待处理" };
coverage.innerHTML = `<span class="status ${coverageStatus.className}">${coverageStatus.label}</span>`;
coverage.innerHTML = `<span class="pill ${pillClass(coverageStatus.className)}">${coverageStatus.label}</span>`;
const parseState = document.createElement("td");
const statusParts = [];
if (batch.exception_sheets > 0) statusParts.push(`${batch.exception_sheets} 个异常`);
if (batch.ignored_sheets > 0) statusParts.push(`${batch.ignored_sheets} 个忽略`);
parseState.textContent = statusParts.length ? statusParts.join("、") : "解析成功";
if (statusParts.length) {
parseState.innerHTML = `<span class="pill pill-warn">${statusParts.join("、")}</span>`;
} else {
parseState.innerHTML = '<span class="pill pill-success">解析成功</span>';
}
const time = document.createElement("td");
time.className = "meta";
time.textContent = String(batch.created_at || "").slice(0, 16).replace("T", " ");
row.append(idCell, bank, period, count, coverage, parseState, time);
const action = document.createElement("td");
action.innerHTML = '<button type="button" class="btn btn-sm" data-batch-view>查看</button>';
row.append(idCell, bank, period, count, coverage, parseState, time, action);
return row;
}
@@ -1835,17 +1928,257 @@ async function loadImportBatches() {
}
const result = await response?.json().catch(() => ({}));
const batches = Array.isArray(result?.batches) ? result.batches : [];
if (batches.length) tbody.replaceChildren(...batches.map(renderBatchRow));
state.batches = batches;
tbody.replaceChildren(...batches.map(renderBatchRow));
const foot = $("#importFoot");
if (foot) foot.textContent = batches.length ? `${batches.length} 个批次` : "暂无批次";
}
function refreshWorkspacePending() {
const card = $("#workspaceTodos");
if (!card) return;
const count = $$(".list-row", card).length;
const status = $("#workspacePendingStatus");
if (status) status.textContent = `${count} 项待处理`;
}
function initReconcile() {
const matchCards = $$("[data-match-card]");
const subjectRows = $$("[data-subject-row]");
if (!matchCards.length && !subjectRows.length) return;
const noticeTitle = $("#notice-title");
const noticeBody = $("#notice-body");
const notice = $("#blocking-notice");
const matchedSummary = $("#matched-summary");
const matchedList = $("#matched-list");
const matchStack = $("#match-stack");
let pendingMatch = matchCards.length;
let pendingSubject = subjectRows.length;
function refreshCounts() {
const countMatch = $("#count-match");
const countSubject = $("#count-subject");
if (countMatch) countMatch.textContent = pendingMatch;
if (countSubject) countSubject.textContent = pendingSubject;
const total = pendingMatch + pendingSubject;
const badge = $('.side-nav a[data-view="reconcile"] .nav-badge');
if (badge) {
badge.textContent = total;
badge.style.display = total ? "" : "none";
}
if (noticeTitle) {
noticeTitle.textContent = total ? `${total} 项待确认,是 7 月结账的阻断项` : "全部确认完成";
}
if (noticeBody) {
noticeBody.textContent = total
? `含单边流水匹配 ${pendingMatch} 项、科目确认 ${pendingSubject} 项。请于 2026-08-29(7 月顺延结账日)前处理完毕,否则集团无法对贵公司执行 7 月结账。`
: "本公司 2026-07 账期已具备结账条件,集团将于 08-29 统一执行结账。";
}
if (notice) {
notice.classList.toggle("warn", total > 0);
notice.classList.toggle("success", total === 0);
}
if (pendingMatch === 0) $('[data-task-type="match"]', $("#workspaceTodos"))?.remove();
const matchRow = $('[data-task-type="match"]', $("#workspaceTodos"));
if (matchRow) {
const title = $(".lr-title", matchRow);
if (title) title.textContent = `处理 ${pendingMatch} 笔单边流水确认`;
}
const subjectRow = $('[data-task-type="subject"]', $("#workspaceTodos"));
if (subjectRow) {
const title = $(".lr-title", subjectRow);
if (title) title.textContent = `确认 ${pendingSubject} 笔其他应收科目`;
}
const flowState = $("#workspaceConfirmState");
if (flowState) flowState.textContent = total ? `待处理 ${total}` : "已完成";
const flowMeta = $("#workspaceConfirmMeta");
if (flowMeta) flowMeta.textContent = total ? `单边流水 ${pendingMatch} 笔 · 科目确认 ${pendingSubject}` : "已全部确认,等待集团结账";
refreshWorkspacePending();
}
matchCards.forEach((card) => {
const confirmButton = $("[data-match-confirm]", card);
const radios = $$('input[type="radio"]', card);
radios.forEach((radio) => radio.addEventListener("change", () => { if (confirmButton) confirmButton.disabled = false; }));
confirmButton?.addEventListener("click", () => {
const summaryText = confirmButton.dataset.matchSummary || "已确认匹配";
const row = document.createElement("div");
row.className = "list-row";
const main = document.createElement("div");
main.className = "lr-main";
const title = document.createElement("div");
title.className = "lr-title";
title.textContent = summaryText;
main.append(title);
const pill = document.createElement("span");
pill.className = "pill pill-success lr-side";
pill.textContent = "已匹配";
row.append(main, pill);
matchedList.append(row);
matchedSummary.style.display = "";
card.remove();
pendingMatch -= 1;
if (pendingMatch === 0) {
const empty = document.createElement("div");
empty.className = "empty";
empty.innerHTML = '<div class="e-title">单边流水已全部匹配</div><div>确认结果将同步至集团审核中心复核</div>';
matchStack.append(empty);
}
refreshCounts();
showToast("匹配已确认", "待办状态、操作人、时间和依据已同步更新");
});
});
$$(".subject-confirm-btn").forEach((button) => {
button.addEventListener("click", () => {
const row = button.closest("[data-subject-row]");
const select = $("select", row);
const subject = select.value;
select.disabled = true;
button.disabled = true;
button.textContent = "已确认";
const statusCell = $(".subject-status", row);
statusCell.innerHTML = `<span class="pill pill-success">已确认 · ${subject}</span>`;
pendingSubject -= 1;
refreshCounts();
showToast("科目已确认", "待办状态、操作人、时间和依据已同步更新");
});
});
const tabMatch = $("#tab-match");
const tabSubject = $("#tab-subject");
const panelMatch = $("#panel-match");
const panelSubject = $("#panel-subject");
function switchTab(which) {
tabMatch?.classList.toggle("active", which === "match");
tabSubject?.classList.toggle("active", which === "subject");
tabMatch?.setAttribute("aria-pressed", String(which === "match"));
tabSubject?.setAttribute("aria-pressed", String(which === "subject"));
if (panelMatch) panelMatch.style.display = which === "match" ? "" : "none";
if (panelSubject) panelSubject.style.display = which === "subject" ? "" : "none";
}
tabMatch?.addEventListener("click", () => switchTab("match"));
tabSubject?.addEventListener("click", () => switchTab("subject"));
refreshCounts();
}
function initNotifications() {
const tabs = $("#notice-tabs");
const list = $("#notice-list");
if (!tabs || !list) return;
const rows = $$(".list-row", list);
const emptyBox = $("#notice-empty");
let currentFilter = "all";
const STATUS_PILL = { unread: "pill-danger", doing: "pill-warn", done: "pill-success" };
const STATUS_LABEL = { unread: "未读", doing: "处理中", done: "已完成" };
function counts() {
const c = { all: rows.length, unread: 0, doing: 0, done: 0 };
rows.forEach((row) => { c[row.getAttribute("data-status")] += 1; });
return c;
}
function refreshCounts() {
const c = counts();
$("#count-all").textContent = c.all;
$("#count-unread").textContent = c.unread;
$("#count-doing").textContent = c.doing;
$("#count-done").textContent = c.done;
const navBadge = $('.side-nav a[data-view="notifications"] .nav-badge');
if (navBadge) {
navBadge.textContent = c.unread;
navBadge.style.display = c.unread ? "" : "none";
}
}
function applyFilter() {
let visible = 0;
rows.forEach((row) => {
const show = currentFilter === "all" || row.getAttribute("data-status") === currentFilter;
row.style.display = show ? "" : "none";
if (show) visible += 1;
});
emptyBox.style.display = visible === 0 ? "" : "none";
}
function setStatus(row, status) {
row.setAttribute("data-status", status);
const pill = $(".pill", row);
if (pill) {
pill.className = `pill ${STATUS_PILL[status]}`;
pill.textContent = STATUS_LABEL[status];
}
}
tabs.addEventListener("click", (event) => {
const btn = event.target.closest("button[data-filter]");
if (!btn) return;
$$("button", tabs).forEach((b) => { b.classList.remove("active"); b.setAttribute("aria-pressed", "false"); });
btn.classList.add("active");
btn.setAttribute("aria-pressed", "true");
currentFilter = btn.dataset.filter;
applyFilter();
});
list.addEventListener("click", (event) => {
const btn = event.target.closest(".btn-mark-read");
if (!btn) return;
const row = btn.closest(".list-row");
setStatus(row, "done");
btn.remove();
refreshCounts();
applyFilter();
});
$("#mark-all-read")?.addEventListener("click", () => {
rows.forEach((row) => {
if (row.getAttribute("data-status") !== "unread") return;
setStatus(row, "done");
$(".btn-mark-read", row)?.remove();
});
refreshCounts();
applyFilter();
showToast("通知已全部标为已读");
});
refreshCounts();
applyFilter();
}
function openAccountDetail(account) {
const meta = companyAccountMeta(account.status);
$("#ad-title").textContent = `${account.bank_name || ""} · 尾号 ${accountTail(account.account_number_masked)}`;
$("#ad-sub").textContent = "本公司 · 登记账户明细";
$("#ad-bank").textContent = account.bank_name || "—";
$("#ad-tail").textContent = `尾号 ${accountTail(account.account_number_masked)}`;
$("#ad-type").textContent = account.account_type || "—";
$("#ad-branch").textContent = account.account_name || "—";
$("#ad-reg").textContent = String(account.created_at || "").slice(0, 10) || "—";
$("#ad-status").textContent = meta.status.label;
$("#ad-audit").textContent = meta.audit.label;
$("#ad-purpose").textContent = "—";
$("#ad-effective").textContent = account.effective_from || "待审核确定";
$("#ad-range").textContent = account.usable ? "尚未上传" : "—";
$("#ad-reason").textContent = account.status === "returned" && account.review_reason ? account.review_reason : "—";
$("#modal-account-detail")?.classList.add("open");
}
function initCompany() {
renderCompanyManualRecords();
loadCompanyAccounts();
loadImportBatches();
const uploadDialog = $("#uploadDialog");
$$('[data-open-upload]').forEach((button) => button.addEventListener("click", () => uploadDialog.showModal()));
$$('[data-close-upload]').forEach((button) => button.addEventListener("click", () => uploadDialog.close()));
uploadDialog?.addEventListener("close", resetUpload);
function openModal(id) { $("#" + id)?.classList.add("open"); }
function closeModal(id) { $("#" + id)?.classList.remove("open"); }
$$("[data-close]").forEach((el) => el.addEventListener("click", () => closeModal(el.dataset.close)));
$$(".modal-backdrop").forEach((bd) => bd.addEventListener("click", (e) => { if (e.target === bd) bd.classList.remove("open"); }));
document.addEventListener("keydown", (e) => { if (e.key === "Escape") $$(".modal-backdrop.open").forEach((m) => m.classList.remove("open")); });
$$('[data-open-upload]').forEach((button) => button.addEventListener("click", () => showView("upload")));
// ── 流水导入:多步流程(选文件 → 解析 → 分 sheet 审核 → 确认) ──
$("#accountSelect")?.addEventListener("change", updateParseButton);
$("#fileInput")?.addEventListener("change", (event) => acceptFile(event.target.files[0]));
$("#removeFile")?.addEventListener("click", () => {
@@ -1858,8 +2191,10 @@ function initCompany() {
});
const dropzone = $("#dropzone");
if (dropzone) {
["dragenter", "dragover"].forEach((type) => dropzone.addEventListener(type, (event) => { event.preventDefault(); dropzone.classList.add("is-dragging"); }));
["dragleave", "drop"].forEach((type) => dropzone.addEventListener(type, (event) => { event.preventDefault(); dropzone.classList.remove("is-dragging"); }));
["dragenter", "dragover"].forEach((type) => dropzone.addEventListener(type, (event) => { event.preventDefault(); dropzone.classList.add("dragover"); }));
["dragleave", "drop"].forEach((type) => dropzone.addEventListener(type, (event) => { event.preventDefault(); dropzone.classList.remove("dragover"); }));
dropzone.addEventListener("click", () => $("#fileInput").click());
dropzone.addEventListener("keydown", (event) => { if (event.key === "Enter" || event.key === " ") { event.preventDefault(); $("#fileInput").click(); } });
dropzone.addEventListener("drop", (event) => acceptFile(event.dataTransfer.files[0]));
}
$("#uploadForm")?.addEventListener("submit", async (event) => {
@@ -1871,7 +2206,7 @@ function initCompany() {
if (parsed && stage === "confirm") {
await confirmImport();
} else if (parsed) {
$("#uploadDialog").close();
resetUpload();
showView("upload");
await loadImportBatches();
} else {
@@ -1884,42 +2219,25 @@ function initCompany() {
await parseFile();
});
function finishReview(button, disposition) {
const item = button.closest("article");
const type = item.dataset.reviewType;
const selectedMatch = $('input[name="matchCandidate"]:checked', item)?.value;
const selectedSubject = $("select", item)?.value;
const detail = type === "match" ? (selectedMatch || "转为匹配异常") : selectedSubject;
const history = $("#reviewHistory");
const record = document.createElement("p");
record.textContent = `${new Date().toLocaleString("zh-CN", { hour12: false })} · 牛女士 · ${disposition} · ${detail}`;
history.append(record);
history.hidden = false;
item.remove();
if (type === "match") $("#matchPendingCount").textContent = "0 笔";
else $("#subjectPendingCount").textContent = "0 笔";
const remaining = $$("#reviewList > article").length;
const badge = $('.side-nav a[data-view="reconcile"] .nav-badge');
if (badge) badge.textContent = remaining;
$(`.cashier-tasks [data-task-type="${type}"]`)?.remove();
const workspaceRemaining = $$(".cashier-tasks > article").length;
$("#workspacePendingStatus").textContent = `${workspaceRemaining} 项待处理`;
const workspaceBadge = $('.side-nav a[data-view="workspace"] .nav-badge');
if (workspaceBadge) workspaceBadge.textContent = workspaceRemaining;
showToast("处理结果已记录", "待办状态、操作人、时间和依据已同步更新");
}
$$('[data-resolve]').forEach((button) => button.addEventListener("click", () => finishReview(button, "确认")));
$$('[data-reject]').forEach((button) => button.addEventListener("click", () => finishReview(button, "转异常")));
$("#markAllRead")?.addEventListener("click", () => {
$$("#companyNotifications .is-unread").forEach((item) => {
item.classList.remove("is-unread");
const status = $(".status", item);
status.className = "status neutral";
status.textContent = "已读";
});
showToast("通知已全部标为已读");
// ── 导入批次详情弹窗 ──
$("#importRows")?.addEventListener("click", (event) => {
const btn = event.target.closest("[data-batch-view]");
if (!btn) return;
const tr = btn.closest("tr");
const batch = (state.batches || []).find((b) => String(b.id) === String(tr?.dataset.batchId));
if (!batch) return;
$("#mb-sub").textContent = `IMP-${String(batch.id).padStart(6, "0")} · ${batch.original_filename || ""}`;
$("#mb-id").textContent = `IMP-${String(batch.id).padStart(6, "0")}`;
$("#mb-bank").textContent = batch.bank_name || "—";
$("#mb-period").textContent = batch.period_start && batch.period_end ? `${batch.period_start} ~ ${batch.period_end}` : "—";
$("#mb-count").textContent = `${batch.confirmed_transactions ?? 0}`;
$("#mb-cover").textContent = batch.status === "exception" ? "未导入" : batch.pending_sheets > 0 ? `${batch.pending_sheets} 个待确认` : batch.confirmed_sheets > 0 ? "已确认" : "待处理";
$("#mb-parse").textContent = batch.exception_sheets > 0 ? `${batch.exception_sheets} 个异常` : batch.ignored_sheets > 0 ? `${batch.ignored_sheets} 个忽略` : "解析成功";
$("#mb-time").textContent = String(batch.created_at || "").slice(0, 16).replace("T", " ");
openModal("modal-batch");
});
// ── 手工记录:提交 + 撤回 ──
$("#manualEntryForm")?.addEventListener("submit", (event) => {
event.preventDefault();
const form = event.currentTarget;
@@ -1955,14 +2273,54 @@ function initCompany() {
showToast("手工记录已提交", "总账复核前不会纳入公司间往来计算");
});
const accountDialog = $("#accountDialog");
$("#openAccountDialog")?.addEventListener("click", () => accountDialog.showModal());
$$('[data-close-account]').forEach((button) => button.addEventListener("click", () => accountDialog.close()));
let manualWithdrawRow = null;
$("#manualRecordRows")?.addEventListener("click", (event) => {
const btn = event.target.closest("button[data-action='withdraw']");
if (!btn) return;
const tr = btn.closest("tr");
manualWithdrawRow = tr;
const cells = tr.cells;
$("#wd-date").textContent = cells[0]?.textContent.trim() || "—";
$("#wd-peer").textContent = cells[2]?.textContent.trim() || "—";
$("#wd-amount").textContent = cells[4]?.textContent.trim() || "—";
$("#wd-summary").textContent = cells[5]?.textContent.trim() || "—";
openModal("withdraw-modal");
});
$("#wd-confirm")?.addEventListener("click", () => {
if (manualWithdrawRow) {
const id = manualWithdrawRow.dataset.storedRecord;
if (id) {
const records = readStoredRecords(storageKeys.manual).filter((r) => r.id !== id);
writeStoredRecords(storageKeys.manual, records);
} else {
manualWithdrawRow.remove();
}
manualWithdrawRow = null;
}
closeModal("withdraw-modal");
renderCompanyManualRecords();
showToast("手工记录已撤回", "该记录已从审核队列中移除,需重新登记提交");
});
// ── 往来确认 ──
initReconcile();
// ── 通知 ──
initNotifications();
// ── 银行账户 ──
$("#openAccountDialog")?.addEventListener("click", () => openModal("accountDialog"));
$("#account-tbody")?.addEventListener("click", (event) => {
const btn = event.target.closest("[data-account-view]");
if (!btn) return;
const tr = btn.closest("tr");
const account = (state.accounts || []).find((a) => String(a.id) === String(tr?.dataset.accountId));
if (!account) return;
openAccountDetail(account);
});
$("#accountForm")?.addEventListener("submit", async (event) => {
event.preventDefault();
const data = new FormData(event.currentTarget);
// The server binds the account to the session company and normalizes the
// number; duplicates come back as 409.
const response = await fetch("/api/company/accounts", {
method: "POST",
headers: { "Content-Type": "application/json" },
@@ -1982,7 +2340,7 @@ function initCompany() {
showToast("账户登记失败", result?.message || "请稍后重试");
return;
}
accountDialog.close();
closeModal("accountDialog");
event.currentTarget.reset();
await loadCompanyAccounts();
showToast("银行账户已提交登记", "复核通过前不能上传流水,也不参与账户识别和覆盖计算");
+709 -52
View File
@@ -23,10 +23,10 @@
<a data-view="upload" href="#upload"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7"><path d="M12 16V4m0 0l-4 4m4-4l4 4"/><path d="M4 15v3a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-3"/></svg><span class="nav-label">流水导入</span></a>
<a data-view="manual" href="#manual"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7"><path d="M12 20h9"/><path d="M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"/></svg><span class="nav-label">手工记录</span></a>
<a data-view="flows" href="#flows"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7"><path d="M4 6h16M4 12h16M4 18h10"/></svg><span class="nav-label">流水管理</span></a>
<a data-view="reconcile" href="#reconcile"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7"><path d="M9 11.5l2 2 4-4.5"/><rect x="4" y="3" width="16" height="18" rx="2"/></svg><span class="nav-label">往来确认</span><span class="nav-badge">2</span></a>
<a data-view="reconcile" href="#reconcile"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7"><path d="M9 11.5l2 2 4-4.5"/><rect x="4" y="3" width="16" height="18" rx="2"/></svg><span class="nav-label">往来确认</span><span class="nav-badge">5</span></a>
<div class="nav-group">账户与消息</div>
<a data-view="accounts" href="#accounts"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7"><rect x="3" y="5" width="18" height="14" rx="2"/><path d="M3 10h18"/></svg><span class="nav-label">银行账户</span></a>
<a data-view="notifications" href="#notifications"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7"><path d="M6 9a6 6 0 1 1 12 0c0 5 2 6 2 6H4s2-1 2-6"/><path d="M10 19a2 2 0 0 0 4 0"/></svg><span class="nav-label">通知</span><span class="nav-badge">2</span></a>
<a data-view="notifications" href="#notifications"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7"><path d="M6 9a6 6 0 1 1 12 0c0 5 2 6 2 6H4s2-1 2-6"/><path d="M10 19a2 2 0 0 0 4 0"/></svg><span class="nav-label">通知</span><span class="nav-badge">3</span></a>
</nav>
<div class="side-foot">
<div class="user-row">
@@ -80,8 +80,8 @@
</a>
<a class="flow-step doing" data-view-link="reconcile" href="#reconcile">
<div class="fs-top"><span class="fs-idx">03</span><span class="fs-dot"></span><span class="fs-name">往来确认</span></div>
<div class="fs-state">待处理 5 笔</div>
<div class="fs-meta">单边流水 3 笔 · 科目确认 2 笔</div>
<div class="fs-state" id="workspaceConfirmState">待处理 5 笔</div>
<div class="fs-meta" id="workspaceConfirmMeta">单边流水 3 笔 · 科目确认 2 笔</div>
</a>
<div class="flow-step wait">
<div class="fs-top"><span class="fs-idx">04</span><span class="fs-dot"></span><span class="fs-name">总账复核</span></div>
@@ -98,12 +98,12 @@
<div class="grid grid-3-2" style="margin-top: 14px;">
<div class="stack">
<div class="card">
<div class="card" id="workspaceTodos">
<div class="card-head">
<span class="card-title">本月待办<span class="sub">本月任务 13/18 · 按处理顺序排列,前 3 项阻断结账</span></span>
<span class="pill pill-warn" id="workspacePendingStatus">4 项待处理</span>
</div>
<div class="list-row">
<div class="list-row" data-task-type="match">
<span class="pill pill-danger">阻断</span>
<div class="lr-main">
<div class="lr-title">处理 3 笔单边流水确认</div>
@@ -111,7 +111,7 @@
</div>
<button class="btn btn-sm" data-view-link="reconcile">去确认</button>
</div>
<div class="list-row">
<div class="list-row" data-task-type="subject">
<span class="pill pill-danger">阻断</span>
<div class="lr-main">
<div class="lr-title">确认 2 笔其他应收科目</div>
@@ -233,73 +233,730 @@
</section>
<section class="app-view" data-page="upload">
<header class="page-heading"><div><h1>流水导入</h1><p>上传 A公司银行账户流水,系统按表头识别银行模板</p></div><button class="button primary" data-open-upload><svg><use href="icons.svg#upload"/></svg>上传流水</button></header>
<section class="panel"><div class="table-summary"><span>最近导入批次</span><span>仅已确认工作表参与匹配与计算</span></div><div class="table-scroll"><table class="data-table"><thead><tr><th>批次</th><th>银行账户</th><th>流水期间</th><th>明细数</th><th>覆盖状态</th><th>解析状态</th><th>上传时间</th></tr></thead><tbody id="importRows"></tbody></table></div></section>
<div class="page-head">
<div>
<h1>流水导入</h1>
<p class="page-sub">上传银行导出的 Excel 流水文件,系统按表头识别银行模板、解析期间与明细并做覆盖校验。当前账期 2026-07,仅已审核通过的账户可导入。</p>
</div>
</div>
<div class="grid grid-1-2">
<div class="card">
<div class="card-head">
<span class="card-title">上传流水<span class="sub">先选账户,再选择或拖入流水文件</span></span>
</div>
<form id="uploadForm" novalidate>
<div class="field" style="margin-bottom: 14px;">
<label for="accountSelect">选择银行账户</label>
<select class="select" id="accountSelect" required><option value="">请选择账户</option></select>
<span class="hint">仅已审核通过的账户可上传;选择账户与文件后方可解析</span>
</div>
<div class="field" style="margin-bottom: 14px;">
<label>流水文件</label>
<input type="file" id="fileInput" accept=".xls,.xlsx" hidden />
<div class="dropzone" id="dropzone" tabindex="0" role="button" aria-label="拖放或点击选择流水文件">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"><path d="M12 16V5m0 0L8 9m4-4l4 4"/><path d="M4 15v3a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-3"/></svg>
<div class="dz-title">拖放银行流水文件到此处</div>
<div class="dz-sub"><span class="dz-browse">点击选择文件</span>,支持银行导出的 Excel</div>
<div class="dz-formats">.XLS / .XLSX · MAX 20MB</div>
</div>
<div class="file-chip" id="filePreview" hidden>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"><path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z"/><path d="M14 3v5h5"/></svg>
<div class="fc-info">
<div class="fc-name" id="fileName"></div>
<div class="fc-meta" id="fileMeta"></div>
</div>
<button type="button" class="fc-remove" id="removeFile" aria-label="移除已选文件">×</button>
</div>
</div>
<div class="notice info" id="parseResult" hidden>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" style="width: 18px; height: 18px; flex: none; margin-top: 1px;"><use href="icons.svg#circle-check"/></svg>
<div style="flex: 1; min-width: 0;">
<div class="n-title" id="parseTitle">文件解析完成</div>
<div class="n-body" id="parseSummary"></div>
</div>
</div>
<div id="sheetReview" hidden>
<div class="card-title" style="margin: 14px 0 6px;">工作表处理</div>
<span class="hint" style="display: block; margin-bottom: 10px;">解析成功不等于业务确认:只有勾选并确认的工作表才会进入后续匹配与计算。</span>
<div id="sheetList" aria-live="polite"></div>
</div>
<button class="btn btn-primary" id="parseButton" type="submit" disabled style="width: 100%; margin-top: 14px;"><span>开始解析</span></button>
<span class="hint" style="display: block; margin-top: 8px; text-align: center;">请先选择流水文件,选择后此按钮方可点击</span>
<div class="detail-box" style="margin-top: 14px;">
<div style="font-weight: 650; margin-bottom: 6px;">导入规则</div>
<div class="stack" style="gap: 5px;">
<div>· 同一账户同一月份重复导入时,新批次覆盖旧批次</div>
<div>· 导入后系统自动与对方公司流水轧算往来余额</div>
<div>· 无法解析的行转入审核中心,不影响其余明细入库</div>
</div>
</div>
</form>
</div>
<div class="card">
<div class="card-head">
<span class="card-title">历史导入批次<span class="sub">2026 年 7 月账期 · 本公司全部账户</span></span>
</div>
<div class="table-wrap" style="border: 0;">
<table class="ds-table">
<thead>
<tr>
<th>批次</th><th>银行账户</th><th>流水期间</th>
<th class="num-col">明细数</th><th>覆盖状态</th><th>解析状态</th>
<th>上传时间</th><th>操作</th>
</tr>
</thead>
<tbody id="importRows"></tbody>
</table>
</div>
<div class="table-foot">
<span id="importFoot">暂无批次</span>
<span class="meta">同一账户同一月份重复导入时,新批次覆盖旧批次</span>
</div>
</div>
</div>
</section>
<section class="app-view" data-page="manual">
<header class="page-heading"><div><h1>手工记录</h1><p>登记个人过账或银行流水之外的往来事实</p></div><span class="status warning" id="manualPendingStatus">1 笔待总账复核</span></header>
<div class="manual-layout">
<form class="panel manual-form" id="manualEntryForm">
<div class="panel-heading"><div><h2>新增记录</h2><p>A公司 · 提交人:牛女士</p></div></div>
<div class="form-body">
<div class="form-grid"><label class="field"><span>交易日期</span><input name="transactionDate" type="date" value="2026-08-06" required /></label><label class="field"><span>收付方向</span><select name="direction" required><option>付款</option><option>收款</option></select></label></div>
<div class="form-grid"><label class="field"><span>金额(元)</span><input name="amount" type="number" min="0.01" step="0.01" required /></label><label class="field"><span>资金来源</span><select name="sourceAccount" required><option value="">请选择</option><option>个人过账</option></select></label></div>
<div class="form-grid"><label class="field"><span>对方类型</span><select name="counterpartyType" required><option>集团内部公司</option><option>个人过账方</option><option>外部单位</option></select></label><label class="field"><span>对方名称</span><input name="counterparty" maxlength="100" placeholder="公司全称或个人姓名" required /></label></div>
<div class="form-grid"><label class="field"><span>对方账号</span><input name="counterpartyAccount" maxlength="64" placeholder="可选" /></label><label class="field"><span>往来科目</span><select name="subject" required><option>应收</option><option>应付</option><option>其他应收</option><option>其他应付</option></select></label></div>
<label class="field"><span>业务摘要</span><input name="summary" maxlength="120" placeholder="例如:个人代付后转回" required /></label>
<label class="field"><span>补充说明</span><textarea name="remark" rows="3" maxlength="500" placeholder="说明形成原因和核对依据" required></textarea></label>
<div class="form-grid"><label class="field"><span>关联银行流水号</span><input name="bankReference" maxlength="80" placeholder="可选" /></label><label class="field"><span>证明附件</span><input name="evidence" type="file" accept=".pdf,.png,.jpg,.jpeg,.xls,.xlsx" /></label></div>
<p class="form-callout"><svg><use href="icons.svg#circle-alert"/></svg>手工记录与银行原始流水分开留痕;总账复核通过后才纳入往来计算。</p>
<div class="page-head">
<div>
<h1>手工记录</h1>
<p class="page-sub">登记未通过银行流水体现的往来业务,如个人过账、现金收付与未入账回单。提交后由集团总账会计审核,通过后计入本公司往来台账。</p>
</div>
</div>
<div class="grid grid-1-2">
<div class="card">
<div class="card-head">
<span class="card-title">登记手工往来<span class="sub">带 * 为必填项,提交后进入待审核状态</span></span>
</div>
<footer><button class="button primary" type="submit"><svg><use href="icons.svg#send"/></svg>提交总账复核</button></footer>
</form>
<section class="panel manual-records-panel">
<div class="panel-heading"><div><h2>本公司记录</h2><p>按提交时间倒序</p></div></div>
<div class="table-scroll"><table class="data-table manual-records-table"><thead><tr><th>编号 / 日期</th><th>方向 / 科目</th><th>资金来源</th><th>对方</th><th>摘要</th><th class="number">金额(元)</th><th>状态</th></tr></thead><tbody id="manualRecordRows"><tr data-demo-record><td><strong>MR-DEMO-001</strong><small>2026.07.29</small></td><td><strong>付款</strong><small>其他应收</small></td><td>个人过账</td><td><strong>B公司</strong><small>集团内部公司</small></td><td>个人代付后转回</td><td class="number">80,000.00</td><td><span class="status warning">待总账复核</span></td></tr></tbody></table></div>
</section>
<form id="manualEntryForm" novalidate>
<div class="grid grid-2" style="gap: 12px;">
<div class="field"><label>交易日期 *</label><input class="input num-input" name="transactionDate" type="date" value="2026-08-06" required /></div>
<div class="field"><label>收付方向 *</label><select class="select" name="direction" required><option>付款</option><option>收款</option></select></div>
<div class="field"><label>金额(元)*</label><input class="input num-input" name="amount" type="number" min="0.01" step="0.01" required /></div>
<div class="field"><label>资金来源 *</label><select class="select" name="sourceAccount" required><option value="">请选择</option><option>个人过账</option></select></div>
<div class="field"><label>对方类型 *</label><select class="select" name="counterpartyType" required><option>集团内部公司</option><option>个人过账方</option><option>外部单位</option></select></div>
<div class="field"><label>对方名称 *</label><input class="input" name="counterparty" maxlength="100" placeholder="公司全称或个人姓名" required /></div>
<div class="field"><label>对方账号</label><input class="input" name="counterpartyAccount" maxlength="64" placeholder="可选" /></div>
<div class="field"><label>往来科目 *</label><select class="select" name="subject" required><option>应收</option><option>应付</option><option>其他应收</option><option>其他应付</option></select></div>
</div>
<div class="field" style="margin-top: 12px;"><label>业务摘要 *</label><input class="input" name="summary" maxlength="120" placeholder="例如:个人代付后转回" required /></div>
<div class="field" style="margin-top: 12px;"><label>补充说明 *</label><textarea class="textarea" name="remark" rows="3" maxlength="500" placeholder="说明形成原因和核对依据" required></textarea></div>
<div class="grid grid-2" style="gap: 12px; margin-top: 12px;">
<div class="field"><label>关联银行流水号</label><input class="input" name="bankReference" maxlength="80" placeholder="可选" /></div>
<div class="field"><label>证明附件</label><input class="input" name="evidence" type="file" accept=".pdf,.png,.jpg,.jpeg,.xls,.xlsx" /></div>
</div>
<div class="notice info" style="margin-top: 14px;">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" style="width: 16px; height: 16px; flex: none; margin-top: 2px; color: var(--info);"><circle cx="12" cy="12" r="8.5"/><path d="M12 8v5M12 16.5v.01"/></svg>
<div><div class="n-title">手工记录与银行原始流水分开留痕</div><div class="n-body">总账复核通过后才纳入往来计算,撤回后需重新登记提交。</div></div>
</div>
<button class="btn btn-primary" type="submit" style="width: 100%; margin-top: 16px;">提交总账复核</button>
</form>
</div>
<div class="card">
<div class="card-head">
<span class="card-title">已提交记录<span class="sub">账期 2026-07 内本公司提交的手工往来,<span class="num" id="manualPendingStatus">0</span> 笔待总账复核</span></span>
</div>
<div class="table-wrap" style="border: 0;">
<table class="ds-table">
<thead>
<tr>
<th>日期</th><th>方向</th><th>对方</th><th>科目</th>
<th class="num-col">金额(元)</th><th class="wrap">摘要</th><th>审核状态</th><th>操作</th>
</tr>
</thead>
<tbody id="manualRecordRows"></tbody>
</table>
</div>
<div class="empty" id="manualEmpty" hidden>
<div class="e-title">暂无已提交记录</div>
登记的手工往来提交后会显示在这里。
</div>
<div class="table-foot">
<span id="manualFoot">共 0 条</span>
<span class="meta">审核由集团总账会计在 1 个工作日内完成</span>
</div>
</div>
</div>
</section>
<section class="app-view" data-page="flows">
<header class="page-heading"><div><h1>流水管理</h1><p>仅查询和导出 A公司的银行流水</p></div><button class="button secondary" id="exportFlows"><svg><use href="icons.svg#download"/></svg>导出当前结果</button></header>
<section class="filter-grid flow-filters"><label class="field"><span>银行</span><select id="flowBank"><option>全部银行</option><option>中信银行</option><option>工商银行</option><option>建设银行</option></select></label><label class="field"><span>账户</span><select id="flowAccount"><option>全部账户</option><option>5316</option><option>9481</option><option>0845</option></select></label><label class="field"><span>开始日期</span><input id="flowStart" type="date" value="2026-07-01" /></label><label class="field"><span>结束日期</span><input id="flowEnd" type="date" value="2026-07-31" /></label><label class="field grow"><span>摘要 / 对方户名 / 流水号</span><input id="flowKeyword" placeholder="输入关键词" /></label><button class="button primary" id="applyFlowFilters"><svg><use href="icons.svg#search"/></svg>查询</button></section>
<section class="panel"><div class="table-summary"><span>当前结果 <strong id="flowCount">3</strong></span><span>金额单位:元</span></div><div class="table-scroll"><table class="data-table" id="flowTable"><thead><tr><th>日期</th><th>银行账户</th><th>方向</th><th>对方户名 / 账号</th><th>摘要</th><th>银行流水号</th><th>归集状态</th><th class="number">金额</th></tr></thead><tbody><tr data-bank="中信银行"><td>2026.07.18</td><td>中信银行 · 5316</td><td>转出</td><td><strong>B公司</strong><small>尾号 9481</small></td><td>往来款</td><td>CIT260718018</td><td><span class="status success">双边匹配</span></td><td class="number">10,000,000.00</td></tr><tr data-bank="建设银行"><td>2026.07.12</td><td>建设银行 · 0845</td><td>转出</td><td><strong>A公司</strong><small>尾号 5316</small></td><td>同名账户调拨</td><td>CCB260712031</td><td><span class="status neutral">同公司调拨</span></td><td class="number">2,000,000.00</td></tr><tr data-bank="中信银行"><td>2026.07.06</td><td>中信银行 · 5316</td><td>转入</td><td><strong>B公司</strong><small>尾号 9481</small></td><td>归还往来款</td><td>CIT260706041</td><td><span class="status success">双边匹配</span></td><td class="number">3,200,000.00</td></tr></tbody></table></div></section>
<div class="page-head">
<div>
<h1>流水管理</h1>
<p class="page-sub">本公司名下银行账户的原始流水及归集状态。集团内往来流水将进入往来确认与归集链路,外部流水不参与归集。</p>
</div>
<div class="page-actions">
<button class="btn btn-primary" id="exportFlows">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" style="width: 15px; height: 15px;"><path d="M12 4v11m0 0l-4-4m4 4l4-4"/><path d="M4 17v2.5A1.5 1.5 0 0 0 5.5 21h13a1.5 1.5 0 0 0 1.5-1.5V17"/></svg>
导出 CSV
</button>
</div>
</div>
<div class="filters">
<div class="field">
<label for="flowBank">银行</label>
<select class="select" id="flowBank"><option>全部银行</option><option>工商银行</option><option>中国银行</option><option>交通银行</option></select>
</div>
<div class="field">
<label for="flowAccount">账户</label>
<select class="select" id="flowAccount"><option>全部账户</option><option>3305</option><option>9916</option><option>7710</option></select>
</div>
<div class="field">
<label for="flowStart">日期起</label>
<input class="input num-input" id="flowStart" type="date" value="2026-07-01" />
</div>
<div class="field">
<label for="flowEnd">日期止</label>
<input class="input num-input" id="flowEnd" type="date" value="2026-08-20" />
</div>
<div class="field" style="min-width: 200px;">
<label for="flowKeyword">关键词</label>
<input class="input" id="flowKeyword" type="text" placeholder="对方单位 / 摘要 / 流水号" />
</div>
<button class="btn" id="resetFlowFilters">重置</button>
<button class="btn btn-primary" id="applyFlowFilters">查询</button>
</div>
<div class="table-wrap">
<table class="ds-table" id="flowTable">
<thead>
<tr>
<th>日期</th>
<th>银行账户</th>
<th>方向</th>
<th class="wrap">对方单位</th>
<th class="wrap">摘要</th>
<th>银行流水号</th>
<th>归集状态</th>
<th class="num-col">金额</th>
</tr>
</thead>
<tbody></tbody>
</table>
<div class="empty" id="flowEmpty" hidden>
<div class="e-title">没有符合条件的流水</div>
调整筛选条件后重新查询。
</div>
<div class="table-foot">
<span id="flowCount">共 0 笔</span>
<span class="num" id="flowSum"></span>
</div>
</div>
</section>
<section class="app-view" data-page="reconcile">
<header class="page-heading"><div><h1>往来确认</h1><p>系统计算为主,只处理无法确定的匹配与科目</p></div></header>
<section class="reconcile-summary"><div><span>自动确认</span><strong>18 笔</strong><small>无需人工处理</small></div><div><span>单边待匹配</span><strong id="matchPendingCount">1 笔</strong><small>需选择对方证据</small></div><div><span>科目待确认</span><strong id="subjectPendingCount">1 笔</strong><small>需选择会计科目</small></div></section>
<section class="panel reconcile-panel"><div class="panel-heading"><div><h2>待确认事项</h2><p>每次确认都会记录操作人、时间和依据</p></div></div><div class="review-list" id="reviewList"><article data-review-type="match"><div class="review-main"><span class="task-level warning">匹配</span><div><h3>2026.07.18 · 转出 280.00 万元</h3><p>中信银行 5316 → B公司 · 摘要“往来款”</p></div></div><fieldset class="candidate-options"><legend>选择对方银行证据</legend><label><input type="radio" name="matchCandidate" value="B公司工商银行 9481" checked /><span><strong>B公司 · 工商银行 9481</strong><small>同金额 · 同日 · 对方账号一致 · 推荐</small></span></label><label><input type="radio" name="matchCandidate" value="B公司建设银行 2046" /><span><strong>B公司 · 建设银行 2046</strong><small>同金额 · 相差 1 日 · 摘要相近</small></span></label></fieldset><div class="review-actions"><button class="button secondary small" data-reject>无法匹配,转异常</button><button class="button primary small" data-resolve="match">确认所选匹配</button></div></article><article data-review-type="subject"><div class="review-main"><span class="task-level warning">科目</span><div><h3>2026.06.27 · 转出 600.00 万元</h3><p>建设银行 0845 → C公司 · 摘要“资金调拨”</p></div></div><div class="candidate"><label class="field compact"><span>确认科目</span><select><option>应收</option><option>其他应收</option></select></label><button class="button primary small" data-resolve="subject">确认科目</button></div></article></div><div class="review-history" id="reviewHistory" hidden><h3>本次处理记录</h3></div></section>
<div class="page-head">
<div>
<h1>往来确认</h1>
<p class="page-sub">处理本公司流水与对方公司对账出现的单边记录,并确认往来科目归属。7 月结账日因故顺延至 08-29,逾期未确认将阻断本公司结账。</p>
</div>
</div>
<div class="notice warn" id="blocking-notice" style="margin-bottom: 16px;">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" style="width: 18px; height: 18px; flex: none; margin-top: 1px;"><path d="M12 8v5M12 16.5v.01"/><path d="M10.3 4.1L2.8 17a2 2 0 0 0 1.7 3h15a2 2 0 0 0 1.7-3L13.7 4.1a2 2 0 0 0-3.4 0z"/></svg>
<div>
<div class="n-title" id="notice-title">5 项待确认,是 7 月结账的阻断项</div>
<div class="n-body" id="notice-body">含单边流水匹配 3 项、科目确认 2 项。请于 2026-08-29(7 月顺延结账日)前处理完毕,否则集团无法对贵公司执行 7 月结账。</div>
</div>
</div>
<div class="tabs">
<button type="button" class="active" id="tab-match" aria-pressed="true">单边流水匹配<span class="tab-count" id="count-match">3</span></button>
<button type="button" id="tab-subject" aria-pressed="false">科目确认<span class="tab-count" id="count-subject">2</span></button>
</div>
<section id="panel-match">
<div class="stack" id="match-stack">
<div class="card" data-match-card>
<div class="card-head">
<span class="card-title">2026-07-14 付 <span class="num amt-out">¥3,200,000.00</span> 给金牛置业<span class="sub">摘要:煤炭采购款</span></span>
<span class="pill pill-danger">单边</span>
</div>
<div class="detail-box" style="margin-bottom: 12px;">
<dl class="kv">
<dt>本方流水号</dt><dd>MY-ICBC-3305-20260714-018</dd>
<dt>本方账户</dt><dd>工商银行基本户 · 尾号 3305</dd>
<dt>对方户名</dt><dd>河南金牛置业有限公司</dd>
</dl>
</div>
<div class="field" style="margin-bottom: 14px;">
<label>候选对方银行证据(选择 1 条进行匹配)</label>
<label class="list-row" style="border: 1px solid var(--border); border-radius: var(--radius); padding: 10px 12px; margin-bottom: 8px; cursor: pointer;">
<input type="radio" name="ev-1" style="width: auto;" />
<div class="lr-main">
<div class="lr-title">金牛置业 · 建设银行一般户 · 尾号 6642</div>
<div class="lr-sub num">2026-07-14 收 ¥3,200,000.00 · 对方流水号 ZY-CCB-6642-20260714-006</div>
</div>
<span class="pill pill-success lr-side">匹配度 高</span>
</label>
<label class="list-row" style="border: 1px solid var(--border); border-radius: var(--radius); padding: 10px 12px; margin-bottom: 8px; cursor: pointer;">
<input type="radio" name="ev-1" style="width: auto;" />
<div class="lr-main">
<div class="lr-title">金牛置业 · 中国银行基本户 · 尾号 8821</div>
<div class="lr-sub num">2026-07-15 收 ¥3,200,000.00 · 对方流水号 ZY-BOC-8821-20260715-011</div>
</div>
<span class="pill pill-warn lr-side">匹配度 中</span>
</label>
<span class="hint">提示:置业中行尾号 8821 账户 07-06 至 07-16 流水断档,其 07-15 记录为对方后补手工记录,匹配度较低,请优先核对建行 6642。</span>
</div>
<div class="row">
<button class="btn btn-primary" data-match-confirm disabled data-match-summary="2026-07-14 付 ¥3,200,000.00 给金牛置业 · 煤炭采购款 · 已匹配置业建行 6642 收款记录">确认匹配</button>
<a class="btn btn-ghost" data-view-link="flows">查看本方流水</a>
</div>
</div>
<div class="card" data-match-card>
<div class="card-head">
<span class="card-title">2026-07-22 付 <span class="num amt-out">¥950,000.00</span> 给金牛物流<span class="sub">摘要:7 月矿区运输费</span></span>
<span class="pill pill-danger">单边</span>
</div>
<div class="detail-box" style="margin-bottom: 12px;">
<dl class="kv">
<dt>本方流水号</dt><dd>MY-ICBC-3305-20260722-031</dd>
<dt>本方账户</dt><dd>工商银行基本户 · 尾号 3305</dd>
<dt>对方户名</dt><dd>河南金牛物流有限公司</dd>
</dl>
</div>
<div class="field" style="margin-bottom: 14px;">
<label>候选对方银行证据(选择 1 条进行匹配)</label>
<label class="list-row" style="border: 1px solid var(--border); border-radius: var(--radius); padding: 10px 12px; margin-bottom: 8px; cursor: pointer;">
<input type="radio" name="ev-2" style="width: auto;" />
<div class="lr-main">
<div class="lr-title">金牛物流 · 农业银行基本户 · 尾号 5508</div>
<div class="lr-sub num">2026-07-22 收 ¥950,000.00 · 对方流水号 WL-ABC-5508-20260722-014</div>
</div>
<span class="pill pill-success lr-side">匹配度 高</span>
</label>
<label class="list-row" style="border: 1px solid var(--border); border-radius: var(--radius); padding: 10px 12px; margin-bottom: 8px; cursor: pointer;">
<input type="radio" name="ev-2" style="width: auto;" />
<div class="lr-main">
<div class="lr-title">金牛物流 · 工商银行一般户 · 尾号 2201</div>
<div class="lr-sub num">2026-07-23 收 ¥950,000.00 · 对方流水号 WL-ICBC-2201-20260723-002</div>
</div>
<span class="pill pill-warn lr-side">匹配度 中</span>
</label>
</div>
<div class="row">
<button class="btn btn-primary" data-match-confirm disabled data-match-summary="2026-07-22 付 ¥950,000.00 给金牛物流 · 7 月矿区运输费 · 已匹配物流农行 5508 收款记录">确认匹配</button>
<a class="btn btn-ghost" data-view-link="flows">查看本方流水</a>
</div>
</div>
<div class="card" data-match-card>
<div class="card-head">
<span class="card-title">2026-07-09 收 <span class="num amt-in">¥1,860,000.00</span> 自金牛贸易<span class="sub">摘要:精煤销售款</span></span>
<span class="pill pill-danger">单边</span>
</div>
<div class="detail-box" style="margin-bottom: 12px;">
<dl class="kv">
<dt>本方流水号</dt><dd>MY-BOC-9916-20260709-007</dd>
<dt>本方账户</dt><dd>中国银行一般户 · 尾号 9916</dd>
<dt>对方户名</dt><dd>河南金牛贸易有限公司</dd>
</dl>
</div>
<div class="field" style="margin-bottom: 14px;">
<label>候选对方银行证据(选择 1 条进行匹配)</label>
<label class="list-row" style="border: 1px solid var(--border); border-radius: var(--radius); padding: 10px 12px; margin-bottom: 8px; cursor: pointer;">
<input type="radio" name="ev-3" style="width: auto;" />
<div class="lr-main">
<div class="lr-title">金牛贸易 · 交通银行基本户 · 尾号 8077</div>
<div class="lr-sub num">2026-07-09 付 ¥1,860,000.00 · 对方流水号 MY-BCM-8077-20260709-019</div>
</div>
<span class="pill pill-success lr-side">匹配度 高</span>
</label>
<label class="list-row" style="border: 1px solid var(--border); border-radius: var(--radius); padding: 10px 12px; margin-bottom: 8px; cursor: pointer;">
<input type="radio" name="ev-3" style="width: auto;" />
<div class="lr-main">
<div class="lr-title">金牛贸易 · 中国银行一般户 · 尾号 3340</div>
<div class="lr-sub num">2026-07-08 付 ¥1,860,000.00 · 对方流水号 MY-BOC-3340-20260708-021</div>
</div>
<span class="pill pill-warn lr-side">匹配度 中</span>
</label>
</div>
<div class="row">
<button class="btn btn-primary" data-match-confirm disabled data-match-summary="2026-07-09 收 ¥1,860,000.00 自金牛贸易 · 精煤销售款 · 已匹配贸易交行 8077 付款记录">确认匹配</button>
<a class="btn btn-ghost" data-view-link="flows">查看本方流水</a>
</div>
</div>
</div>
<div class="card" style="margin-top: 14px; display: none;" id="matched-summary">
<div class="card-head">
<span class="card-title">已匹配记录<span class="sub">本页已确认的单边流水</span></span>
</div>
<div id="matched-list"></div>
</div>
</section>
<section id="panel-subject" style="display: none;">
<div class="card" style="padding: 0;">
<div class="table-wrap" style="border: 0;">
<table class="ds-table">
<thead>
<tr>
<th>日期</th><th>对方</th><th class="num-col">金额</th><th>摘要</th><th>当前建议科目</th><th>科目确认</th><th>状态</th>
</tr>
</thead>
<tbody>
<tr data-subject-row>
<td class="num">2026-07-18</td>
<td class="cell-main">金牛新能源</td>
<td class="num-col amt-in">¥680,000.00</td>
<td>矿区光伏场地租赁费</td>
<td><span class="tag">其他应收</span></td>
<td>
<div class="row" style="gap: 8px;">
<select class="select" style="min-height: 30px; padding: 4px 8px;"><option>应收</option><option>应付</option><option selected>其他应收</option><option>其他应付</option></select>
<button class="btn btn-sm btn-primary subject-confirm-btn">确认</button>
</div>
</td>
<td class="subject-status"><span class="pill pill-warn">待确认</span></td>
</tr>
<tr data-subject-row>
<td class="num">2026-07-25</td>
<td class="cell-main">金牛农业</td>
<td class="num-col amt-out">¥420,000.00</td>
<td>职工食堂食材统采分摊</td>
<td><span class="tag">其他应付</span></td>
<td>
<div class="row" style="gap: 8px;">
<select class="select" style="min-height: 30px; padding: 4px 8px;"><option>应收</option><option>应付</option><option>其他应收</option><option selected>其他应付</option></select>
<button class="btn btn-sm btn-primary subject-confirm-btn">确认</button>
</div>
</td>
<td class="subject-status"><span class="pill pill-warn">待确认</span></td>
</tr>
</tbody>
</table>
</div>
<div class="table-foot">
<span>共 2 条待确认 · 确认后可在「流水管理」中继续查看</span>
<span class="num">合计 ¥1,100,000.00</span>
</div>
</div>
</section>
</section>
<section class="app-view" data-page="accounts">
<header class="page-heading"><div><h1>银行账户</h1><p>A公司登记账户与流水覆盖区间</p></div><button class="button secondary" id="openAccountDialog"><svg><use href="icons.svg#plus"/></svg>登记账户</button></header>
<section class="account-directory" id="accountDirectory"></section>
<div class="page-head">
<div>
<h1>银行账户</h1>
<p class="page-sub">本公司名下已登记银行账户。仅审核通过的账户可上传流水,账号对外仅显示尾号。</p>
</div>
<div class="page-actions">
<button class="btn btn-primary" id="openAccountDialog">提交新账户登记</button>
</div>
</div>
<div class="notice info" style="margin-bottom: 14px;">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" style="width: 16px; height: 16px; flex: none; margin-top: 2px; color: var(--info);"><circle cx="12" cy="12" r="8.5"/><path d="M12 8v5M12 16.5v.01"/></svg>
<div>
<div class="n-title">新账户需总账管理员审核通过后方可上传流水</div>
<div class="n-body">提交登记后由集团总账管理员核对开户信息,审核周期一般 1 个工作日;审核结果将通过站内通知告知。</div>
</div>
</div>
<div class="card">
<div class="card-head">
<span class="card-title">本公司账户<span class="sub"><span id="account-count">0</span> 个 · 统计截止 2026-08-20</span></span>
</div>
<div class="table-wrap" style="border: 0;">
<table class="ds-table">
<thead>
<tr>
<th>银行 / 账号</th>
<th>账户类型</th>
<th>启用状态</th>
<th>流水覆盖区间</th>
<th>审核状态</th>
<th>操作</th>
</tr>
</thead>
<tbody id="account-tbody"></tbody>
</table>
</div>
<div class="empty" id="account-empty" hidden>
<div class="e-title">暂无已登记账户</div>
点击右上角「提交新账户登记」提交开户信息。
</div>
<div class="table-foot">
<span id="account-foot">共 0 个账户</span>
<span class="meta">仅审核通过的账户可上传流水并参与归集</span>
</div>
</div>
</section>
<section class="app-view" data-page="notifications">
<header class="page-heading"><div><h1>通知</h1><p>系统提醒与总账管理员通知</p></div><button class="button secondary" id="markAllRead"><svg><use href="icons.svg#circle-check"/></svg>全部标为已读</button></header>
<section class="panel notification-page"><div class="notification-list" id="companyNotifications"><article class="is-unread"><span class="notification-icon danger"><svg><use href="icons.svg#circle-alert"/></svg></span><span><strong>7 月工商银行流水尚未上传</strong><small>系统自动提醒 · 今天 08:00 · 截止 08.05</small><p>账户尾号 9481 缺少 07.01—07.21 流水。</p></span><em class="status danger">未读</em></article><article class="is-unread"><span class="notification-icon warning"><svg><use href="icons.svg#bell"/></svg></span><span><strong>请完成 7 月银行流水确认</strong><small>系统管理员 · 今天 09:30 · 截止 08.08</small><p>请先处理断档和两项待确认往来,再提交公司确认。</p></span><em class="status warning">未读</em></article><article><span class="notification-icon success"><svg><use href="icons.svg#circle-check"/></svg></span><span><strong>6 月账期已完成集团结账</strong><small>系统通知 · 07.05 18:10</small></span><em class="status neutral">已读</em></article></div></section>
<div class="page-head">
<div>
<h1>通知</h1>
<p class="page-sub">系统与总账管理员发送给本公司的全部消息。7 月账期结账因故顺延至 2026-08-29,请优先处理未读与处理中事项。</p>
</div>
<div class="page-actions">
<button class="btn btn-ghost" id="mark-all-read">全部标为已读</button>
</div>
</div>
<div class="card">
<div class="tabs" id="notice-tabs">
<button type="button" class="active" data-filter="all" aria-pressed="true">全部<span class="tab-count" id="count-all">9</span></button>
<button type="button" data-filter="unread" aria-pressed="false">未读<span class="tab-count" id="count-unread">3</span></button>
<button type="button" data-filter="doing" aria-pressed="false">处理中<span class="tab-count" id="count-doing">2</span></button>
<button type="button" data-filter="done" aria-pressed="false">已完成<span class="tab-count" id="count-done">4</span></button>
</div>
<div id="notice-list">
<div class="list-row" data-status="unread">
<span class="pill pill-danger" style="flex: none;">未读</span>
<span class="tag">系统</span>
<div class="lr-main">
<div class="lr-title">7 月流水存在断档风险:交行 尾号 7710 未上传</div>
<div class="lr-sub"><span class="meta">2026-08-20 14:32</span> · 交行 7710 账户(待审核)7 月流水尚未上传,距 7 月结账日(顺延至 08-29)仅剩 9 天,请尽快补传。</div>
</div>
<div class="lr-side"><button class="btn btn-sm btn-mark-read">标记已读</button></div>
</div>
<div class="list-row" data-status="unread">
<span class="pill pill-danger" style="flex: none;">未读</span>
<span class="tag">系统</span>
<div class="lr-main">
<div class="lr-title">您有 2 笔单边流水待选择对方证据</div>
<div class="lr-sub"><span class="meta">2026-08-19 09:15</span> · 与金牛置业的煤炭采购款 ¥3,200,000.00 等 2 笔流水仅有本方记录,需选择对方银行流水佐证。</div>
</div>
<div class="lr-side"><button class="btn btn-sm btn-mark-read">标记已读</button></div>
</div>
<div class="list-row" data-status="unread">
<span class="pill pill-danger" style="flex: none;">未读</span>
<span class="tag">总账管理员</span>
<div class="lr-main">
<div class="lr-title">总账管理员提醒:请于 08-29 前完成待确认事项</div>
<div class="lr-sub"><span class="meta">2026-08-18 16:40</span> · 7 月账期结账顺延至 08-29,贵公司当前仍有 5 项往来确认未完成,请合理安排时间。</div>
</div>
<div class="lr-side"><button class="btn btn-sm btn-mark-read">标记已读</button></div>
</div>
<div class="list-row" data-status="doing">
<span class="pill pill-warn" style="flex: none;">处理中</span>
<span class="tag">系统</span>
<div class="lr-main">
<div class="lr-title">手工记录科目待确认:其他应收 ¥86,500.00</div>
<div class="lr-sub"><span class="meta">2026-08-15 10:02</span> · 07-28 录入的矿区备用金垫付记录,往来科目「其他应收」待总账管理员复核。</div>
</div>
<div class="lr-side"><button class="btn btn-sm btn-primary" data-view-link="manual">去处理</button></div>
</div>
<div class="list-row" data-status="doing">
<span class="pill pill-warn" style="flex: none;">处理中</span>
<span class="tag">总账管理员</span>
<div class="lr-main">
<div class="lr-title">往来确认回复:置业已确认 320 万煤炭采购款</div>
<div class="lr-sub"><span class="meta">2026-08-12 11:26</span> · 金牛置业已确认 07-14 煤炭采购款 ¥3,200,000.00,请补充本方工行 3305 账户流水证据完成闭环。</div>
</div>
<div class="lr-side"><button class="btn btn-sm btn-primary" data-view-link="reconcile">去处理</button></div>
</div>
<div class="list-row" data-status="done">
<span class="pill pill-success" style="flex: none;">已完成</span>
<span class="tag">系统</span>
<div class="lr-main">
<div class="lr-title">流水导入成功:中行 尾号 9916 · 42 笔</div>
<div class="lr-sub"><span class="meta">2026-08-06 15:52</span> · 7 月中行一般户流水已导入,42 笔全部校验通过,无重复记录。</div>
</div>
</div>
<div class="list-row" data-status="done">
<span class="pill pill-success" style="flex: none;">已完成</span>
<span class="tag">系统</span>
<div class="lr-main">
<div class="lr-title">流水导入成功:工行 尾号 3305 · 86 笔</div>
<div class="lr-sub"><span class="meta">2026-08-06 15:48</span> · 7 月工行基本户流水已导入,86 笔全部校验通过,无重复记录。</div>
</div>
</div>
<div class="list-row" data-status="done">
<span class="pill pill-success" style="flex: none;">已完成</span>
<span class="tag">总账管理员</span>
<div class="lr-main">
<div class="lr-title">交行 尾号 7710 账户开户资料已受理</div>
<div class="lr-sub"><span class="meta">2026-07-22 13:10</span> · 新账户开户资料已提交总账管理员,审核通过后方可启用并上传流水。</div>
</div>
</div>
<div class="list-row" data-status="done">
<span class="pill pill-success" style="flex: none;">已完成</span>
<span class="tag">系统</span>
<div class="lr-main">
<div class="lr-title">2026 年 6 月账期已结账</div>
<div class="lr-sub"><span class="meta">2026-07-05 09:00</span> · 6 月账期已按每月 5 日结账规则完成结账,期末数据已锁定,不可再修改。</div>
</div>
</div>
</div>
<div class="empty" id="notice-empty" style="display: none;">
<div class="e-title">暂无该状态的通知</div>
<div>切换其他状态查看,或等待新的系统与管理员消息。</div>
</div>
</div>
</section>
</main>
</div>
</div>
<dialog id="uploadDialog" class="dialog upload-dialog">
<form id="uploadForm">
<header><div><h2>上传银行流水</h2><p>A公司 · 系统将识别表头与银行模板</p></div><button type="button" class="icon-button" data-close-upload aria-label="关闭" title="关闭"><svg><use href="icons.svg#x"/></svg></button></header>
<div class="dialog-body"><label class="field"><span>银行账户</span><select id="accountSelect" required><option value="">请选择账户</option></select></label><label class="dropzone" id="dropzone"><input id="fileInput" type="file" accept=".xls,.xlsx" /><svg><use href="icons.svg#upload"/></svg><strong>选择或拖入银行流水文件</strong><span>支持 .xls 与 .xlsx,最大 20 MB</span></label><div class="file-preview" id="filePreview" hidden><span class="file-type"><svg><use href="icons.svg#file-spreadsheet"/></svg></span><span><strong id="fileName"></strong><small id="fileMeta"></small></span><button type="button" class="icon-button" id="removeFile" aria-label="移除文件" title="移除文件"><svg><use href="icons.svg#x"/></svg></button></div><div class="parse-result" id="parseResult" hidden><span class="notification-icon"><svg><use href="icons.svg#circle-check"/></svg></span><span><strong id="parseTitle">文件解析完成</strong><p id="parseSummary"></p></span></div><div class="sheet-review" id="sheetReview" hidden><h3>工作表处理</h3><p class="sheet-review-hint">解析成功不等于业务确认:只有勾选并确认的工作表才会进入后续匹配与计算。</p><div class="sheet-list" id="sheetList" aria-live="polite"></div></div></div>
<footer><button type="button" class="button secondary" data-close-upload>取消</button><button class="button primary" id="parseButton" type="submit" disabled><span>开始解析</span></button></footer>
</form>
</dialog>
<dialog id="accountDialog" class="dialog">
<form id="accountForm">
<header><div><h2>登记银行账户</h2><p>账户提交后由总账管理员复核</p></div><button type="button" class="icon-button" data-close-account aria-label="关闭" title="关闭"><svg><use href="icons.svg#x"/></svg></button></header>
<div class="dialog-body"><div class="form-grid"><label class="field"><span>开户银行</span><input name="bank" placeholder="例如:郑州银行" required /></label><label class="field"><span>账户类型</span><select name="type"><option>基本户</option><option>一般户</option><option>专用户</option></select></label></div><label class="field"><span>银行账</span><input name="accountNumber" inputmode="numeric" required /></label><label class="field"><span>启用日期</span><input name="startDate" type="date" value="2026-08-06" required /></label></div>
<footer><button type="button" class="button secondary" data-close-account>取消</button><button class="button primary" type="submit">提交登记</button></footer>
</form>
</dialog>
<!-- 导入批次详情弹窗 -->
<div class="modal-backdrop" id="modal-batch">
<div class="modal">
<div class="modal-head">
<span class="modal-title">导入批次详情</span>
<button type="button" class="modal-close" data-close="modal-batch" aria-label="关闭">×</button>
</div>
<p class="modal-sub" id="mb-sub"></p>
<div class="detail-box">
<dl class="kv">
<dt>批次</dt><dd id="mb-id"></dd>
<dt>账户</dt><dd id="mb-bank"></dd>
<dt>流水期间</dt><dd id="mb-period"></dd>
<dt>明细数量</dt><dd id="mb-count"></dd>
<dt>覆盖状态</dt><dd id="mb-cover"></dd>
<dt>解析状态</dt><dd id="mb-parse"></dd>
<dt>导入人</dt><dd id="mb-user">本公司出纳</dd>
<dt>导入时间</dt><dd id="mb-time"></dd>
</dl>
</div>
<div class="modal-actions">
<button type="button" class="btn" data-close="modal-batch">关闭</button>
</div>
</div>
</div>
<!-- 撤回手工记录弹窗 -->
<div class="modal-backdrop" id="withdraw-modal">
<div class="modal">
<div class="modal-head">
<span class="modal-title">撤回手工记录</span>
<button type="button" class="modal-close" data-close="withdraw-modal" aria-label="关闭">×</button>
</div>
<p class="modal-sub">撤回后该记录将从审核队列中移除,且不可恢复,需重新登记提交。</p>
<div class="detail-box">
<dl class="kv">
<dt>业务日期</dt><dd id="wd-date"></dd>
<dt>对方公司</dt><dd id="wd-peer"></dd>
<dt>金额</dt><dd id="wd-amount"></dd>
<dt>摘要</dt><dd id="wd-summary" style="font-family: var(--font-body);"></dd>
</dl>
</div>
<div class="modal-actions">
<button type="button" class="btn" data-close="withdraw-modal">取消</button>
<button type="button" class="btn btn-danger" id="wd-confirm">确认撤回</button>
</div>
</div>
</div>
<!-- 流水详情弹窗 -->
<div class="modal-backdrop" id="tx-modal">
<div class="modal wide">
<div class="modal-head">
<span class="modal-title">流水详情</span>
<button type="button" class="modal-close" id="tx-modal-close" aria-label="关闭">×</button>
</div>
<p class="modal-sub" id="tx-modal-sub"></p>
<div class="detail-box">
<div class="card-title" style="margin-bottom: 10px;">原始流水</div>
<dl class="kv">
<dt>银行流水号</dt><dd id="d-serial"></dd>
<dt>银行</dt><dd id="d-bank"></dd>
<dt>账户</dt><dd id="d-account"></dd>
<dt>交易时间</dt><dd id="d-time"></dd>
<dt>对方户名</dt><dd id="d-peer"></dd>
<dt>对方账号</dt><dd id="d-peer-acct"></dd>
<dt>金额</dt><dd id="d-amount"></dd>
</dl>
</div>
<div class="detail-box" style="margin-top: 12px;">
<div class="card-title" style="margin-bottom: 10px;">归集链路</div>
<dl class="kv">
<dt>归集状态</dt><dd id="d-status"></dd>
<dt>归集往来</dt><dd id="d-pair"></dd>
<dt>本方科目</dt><dd id="d-subject"></dd>
<dt>归集批次</dt><dd id="d-batch"></dd>
<dt>说明</dt><dd id="d-note" style="font-family: var(--font-body);"></dd>
</dl>
</div>
<div class="modal-actions">
<button type="button" class="btn" id="tx-modal-ok">关闭</button>
</div>
</div>
</div>
<!-- 账户明细弹窗 -->
<div class="modal-backdrop" id="modal-account-detail">
<div class="modal">
<div class="modal-head">
<span class="modal-title" id="ad-title">账户明细</span>
<button type="button" class="modal-close" data-close="modal-account-detail" aria-label="关闭">×</button>
</div>
<p class="modal-sub" id="ad-sub">本公司 · 登记账户明细</p>
<div class="detail-box" style="margin-bottom: 12px;">
<dl class="kv">
<dt>开户银行</dt><dd id="ad-bank"></dd>
<dt>账号尾号</dt><dd id="ad-tail"></dd>
<dt>账户类型</dt><dd id="ad-type"></dd>
<dt>开户行名称</dt><dd id="ad-branch"></dd>
<dt>登记日期</dt><dd id="ad-reg"></dd>
<dt>启用状态</dt><dd id="ad-status"></dd>
<dt>审核状态</dt><dd id="ad-audit"></dd>
<dt>用途说明</dt><dd id="ad-purpose" style="font-family: var(--font-body);"></dd>
</dl>
</div>
<div class="detail-box">
<dl class="kv">
<dt>申请启用</dt><dd id="ad-effective"></dd>
<dt>流水覆盖区间</dt><dd id="ad-range"></dd>
<dt>退回原因</dt><dd id="ad-reason" style="font-family: var(--font-body);"></dd>
</dl>
</div>
<div class="modal-actions">
<button type="button" class="btn" data-close="modal-account-detail">关闭</button>
</div>
</div>
</div>
<!-- 登记银行账户弹窗 -->
<div class="modal-backdrop" id="accountDialog">
<div class="modal">
<div class="modal-head">
<span class="modal-title">登记银行账户</span>
<button type="button" class="modal-close" data-close="accountDialog" aria-label="关闭">×</button>
</div>
<p class="modal-sub">账户提交后由总账管理员复核,复核通过前不能上传流水,也不参与账户识别与覆盖计算。</p>
<form id="accountForm" novalidate>
<div class="grid grid-2">
<div class="field">
<label>开户银行</label>
<input class="input" name="bank" placeholder="例如:郑州银行" required />
</div>
<div class="field">
<label>账户类型</label>
<select class="select" name="type"><option>基本户</option><option>一般户</option><option>专用户</option></select>
</div>
</div>
<div class="field" style="margin-top: 12px;">
<label>银行账号</label>
<input class="input num-input" name="accountNumber" inputmode="numeric" required />
<span class="hint">仅尾号对外显示,完整账号仅总账管理员可见</span>
</div>
<div class="field" style="margin-top: 12px;">
<label>启用日期</label>
<input class="input num-input" name="startDate" type="date" required />
</div>
<div class="modal-actions">
<button type="button" class="btn" data-close="accountDialog">取消</button>
<button type="submit" class="btn btn-primary">提交登记</button>
</div>
</form>
</div>
</div>
<div class="toast-region" id="toastRegion" aria-live="polite"></div>
<script src="app.js"></script>
</body>