diff --git a/web/app.js b/web/app.js
index b0ca75f..d80d472 100644
--- a/web/app.js
+++ b/web/app.js
@@ -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 = `${record.subject || "—"}`;
+
+ 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 = `${statusMeta.label}`;
+
+ const action = document.createElement("td");
+ if (record.status === "待总账复核") {
+ action.innerHTML = '';
+ } else {
+ action.innerHTML = '—';
+ }
+
+ 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 = `${account.account_type || "—"}`;
+
+ const meta = companyAccountMeta(account.status);
+ const statusCell = document.createElement("td"); statusCell.innerHTML = `${meta.status.label}`;
+
+ const coverage = document.createElement("td");
+ coverage.className = "num muted";
+ coverage.textContent = account.usable ? "尚未上传" : "—";
+
+ const auditCell = document.createElement("td"); auditCell.innerHTML = `${meta.audit.label}`;
+
+ const action = document.createElement("td"); action.innerHTML = '';
+
+ 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 `
+ const data = currentFlowData();
+ tbody.innerHTML = data.map((row, index) => {
+ const companyCell = portal === "admin" ? `| ${row.company} | ` : "";
+ return `
| ${row.date} |
- ${row.company} |
+ ${companyCell}
${row.acctLabel} |
${row.dir} |
${row.peer} |
@@ -1423,13 +1482,21 @@ function renderFlowRows() {
`;
}).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 ? `收 +¥ ${formatCurrency(inflow)} · 付 -¥ ${formatCurrency(outflow)}` : "";
+ }
}
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 ? `收 +¥ ${formatCurrency(inflow)} · 付 -¥ ${formatCurrency(outflow)}` : "";
+ }
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 = `${coverageStatus.label}`;
+ coverage.innerHTML = `${coverageStatus.label}`;
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 = `${statusParts.join("、")}`;
+ } else {
+ parseState.innerHTML = '解析成功';
+ }
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 = '';
+
+ 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 = '单边流水已全部匹配
确认结果将同步至集团审核中心复核
';
+ 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 = `已确认 · ${subject}`;
+ 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("银行账户已提交登记", "复核通过前不能上传流水,也不参与账户识别和覆盖计算");
diff --git a/web/company.html b/web/company.html
index f292de8..e4fe33e 100644
--- a/web/company.html
+++ b/web/company.html
@@ -23,10 +23,10 @@
流水导入
手工记录
流水管理
- 往来确认2
+ 往来确认5
账户与消息
银行账户
- 通知2
+ 通知3