HEL-122: 第3批——管理端剩余6页 + 对应弹窗换原型壳
Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
+511
-164
@@ -126,6 +126,10 @@ function accountStatusLabel(status) {
|
||||
return accountStatusLabels[status] || "待复核";
|
||||
}
|
||||
|
||||
function pillClass(statusClass) {
|
||||
return { success: "pill-success", danger: "pill-danger", warning: "pill-warn", neutral: "pill-muted", info: "pill-info" }[statusClass] || "pill-muted";
|
||||
}
|
||||
|
||||
function accountTail(masked) {
|
||||
return String(masked || "").replace(/^\*+/, "");
|
||||
}
|
||||
@@ -541,14 +545,16 @@ function pairData(from, to, endDate) {
|
||||
};
|
||||
}
|
||||
|
||||
const pairSubjectOrder = ["应收", "其他应收", "应付", "其他应付"];
|
||||
|
||||
function setPair(from, to, endDate = "2026-07-31") {
|
||||
$$('[data-pair-from]').forEach((item) => { item.textContent = from; });
|
||||
$$('[data-pair-to]').forEach((item) => { item.textContent = to; });
|
||||
$$("[data-pair-form]").forEach((form) => {
|
||||
const fromSelect = $('[name="from"]', form);
|
||||
const toSelect = $('[name="to"]', form);
|
||||
if ([...fromSelect.options].some((option) => option.value === from)) fromSelect.value = from;
|
||||
if ([...toSelect.options].some((option) => option.value === to)) toSelect.value = to;
|
||||
if (fromSelect && [...fromSelect.options].some((option) => option.value === from)) fromSelect.value = from;
|
||||
if (toSelect && [...toSelect.options].some((option) => option.value === to)) toSelect.value = to;
|
||||
const endInput = $('[name="end"]', form);
|
||||
if (endInput) endInput.value = endDate;
|
||||
});
|
||||
@@ -556,20 +562,56 @@ function setPair(from, to, endDate = "2026-07-31") {
|
||||
const data = pairData(from, to, endDate);
|
||||
const format = (value) => value.toLocaleString("zh-CN", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
||||
$("#pairPeriod").textContent = `统计口径 2026.01.01—${endDate}`;
|
||||
$("#pairOpening").textContent = format(data.opening);
|
||||
$("#pairDebit").textContent = format(data.debit);
|
||||
$("#pairCredit").textContent = format(data.credit);
|
||||
$("#pairFinal").innerHTML = `${data.final >= 0 ? "应收" : "应付"} ${format(Math.abs(data.final))}<small>万元</small>`;
|
||||
$("#pairOpening").innerHTML = `${format(data.opening)}<span class="unit">万元</span>`;
|
||||
$("#pairDebit").innerHTML = `${format(data.debit)}<span class="unit">万元</span>`;
|
||||
$("#pairCredit").innerHTML = `${format(data.credit)}<span class="unit">万元</span>`;
|
||||
$("#pairFinal").innerHTML = `${data.final >= 0 ? "应收" : "应付"} ${format(Math.abs(data.final))}<span class="unit">万元</span>`;
|
||||
$("#pairReviewStatus").textContent = "含 1 笔待审核";
|
||||
const quickResult = $(".quick-result");
|
||||
if (quickResult) {
|
||||
$("span", quickResult).textContent = `${from}对${to}`;
|
||||
$("strong", quickResult).innerHTML = `${data.final >= 0 ? "应收" : "应付"} ${format(Math.abs(data.final))}<small>万元</small>`;
|
||||
$("p", quickResult).textContent = `${data.rows.length - 1} 笔已确认 · 1 笔待审核`;
|
||||
}
|
||||
$('[data-subject-total="all"]').textContent = `${data.rows.length} 笔`;
|
||||
Object.entries(data.totals).forEach(([subject, value]) => { $(`[data-subject-total="${subject}"]`).textContent = format(value); });
|
||||
$("#pairTransactions tbody").innerHTML = data.rows.map(([date, direction, subject, ownAccount, counterparty, summary, match, amount]) => `<tr data-subject="${subject}"><td>${date}</td><td>${direction}</td><td>${subject}</td><td>${ownAccount}</td><td>${counterparty}</td><td>${summary}</td><td><span class="status ${match === "双边匹配" ? "success" : "warning"}">${match}</span></td><td class="number">${format(amount)}</td></tr>`).join("");
|
||||
Object.entries(data.totals).forEach(([subject, value]) => { $(`[data-subject-total="${subject}"]`)?.replaceChildren(document.createTextNode(format(value))); });
|
||||
state.pairContext = { from, to, endDate };
|
||||
state.pairRows = data.rows.map(([date, direction, subject, ownAccount, counterparty, summary, match, amount]) => ({ date, direction, subject, ownAccount, counterparty, summary, match, amount }));
|
||||
renderPairRows();
|
||||
}
|
||||
|
||||
function renderPairRows() {
|
||||
const tbody = $("#pairTransactions");
|
||||
if (!tbody) return;
|
||||
const format = (value) => Number(value).toLocaleString("zh-CN", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
||||
tbody.innerHTML = state.pairRows.map((row, index) => {
|
||||
const matched = row.match === "双边匹配";
|
||||
return `<tr data-subject="${row.subject}" data-pair-idx="${index}">
|
||||
<td class="num">${row.date}</td>
|
||||
<td>${row.direction}</td>
|
||||
<td>${row.subject}</td>
|
||||
<td>${row.ownAccount}</td>
|
||||
<td>${row.counterparty}</td>
|
||||
<td class="wrap">${row.summary}</td>
|
||||
<td><span class="pill ${matched ? "pill-success" : "pill-warn"}">${row.match}</span></td>
|
||||
<td class="num-col ${row.direction === "转出" ? "amt-out" : "amt-in"}">${format(row.amount)}</td>
|
||||
<td><button type="button" class="btn btn-sm" data-trace="${index}">穿透</button></td>
|
||||
</tr>`;
|
||||
}).join("");
|
||||
}
|
||||
|
||||
function openTrace(index) {
|
||||
const modal = $("#traceModal");
|
||||
if (!modal) return;
|
||||
const row = state.pairRows?.[index];
|
||||
if (!row) return;
|
||||
const ctx = state.pairContext || { from: "—", to: "—" };
|
||||
$("#traceSub").textContent = `${ctx.from} ↔ ${ctx.to} · ${row.subject} · ${row.date} · ${row.direction} ${formatCurrency(row.amount)} 万元`;
|
||||
$("#kvOwnTx").textContent = "—(演示数据,未关联银行流水)";
|
||||
$("#kvOwnAcct").textContent = row.ownAccount;
|
||||
$("#kvPeerCo").textContent = ctx.to;
|
||||
$("#kvPeerAcct").textContent = row.counterparty;
|
||||
$("#kvTime").textContent = row.date;
|
||||
$("#kvAmt").textContent = `${formatCurrency(row.amount)} 万元`;
|
||||
$("#kvBatch").textContent = "—";
|
||||
const evidence = $("#kvEvidence");
|
||||
if (row.match === "双边匹配") { evidence.textContent = "—"; evidence.style.color = ""; }
|
||||
else { evidence.textContent = "待对方提供"; evidence.style.color = "var(--warn)"; }
|
||||
modal.classList.add("open");
|
||||
}
|
||||
|
||||
function initPairQueries() {
|
||||
@@ -586,13 +628,17 @@ function initPairQueries() {
|
||||
const from = $('[name="from"]', form).value;
|
||||
const to = $('[name="to"]', form).value;
|
||||
const endDate = $('[name="end"]', form)?.value || "2026-07-31";
|
||||
const error = $("#pairError");
|
||||
if (from === to) {
|
||||
if (error) { error.style.display = ""; }
|
||||
showToast("请选择两个不同的公司", "同公司账户调拨不进入公司间往来查询");
|
||||
return;
|
||||
}
|
||||
if (error) error.style.display = "none";
|
||||
const notice = $("#pairNotice");
|
||||
if (notice) notice.style.display = "none";
|
||||
setPair(from, to, endDate);
|
||||
showView("pair");
|
||||
showToast("查询结果已更新", `${from} 与 ${to} · 截至 ${endDate}`);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -606,11 +652,19 @@ function initPairQueries() {
|
||||
const subject = button.dataset.subjectFilter;
|
||||
$$("[data-subject-filter]").forEach((item) => {
|
||||
const active = item === button;
|
||||
item.classList.toggle("is-active", active);
|
||||
item.classList.toggle("active", active);
|
||||
item.setAttribute("aria-pressed", String(active));
|
||||
});
|
||||
$$("#pairTransactions tbody tr").forEach((row) => { row.hidden = subject !== "all" && row.dataset.subject !== subject; });
|
||||
$$("#pairTransactions tr").forEach((row) => { row.hidden = subject !== "all" && row.dataset.subject !== subject; });
|
||||
}));
|
||||
|
||||
$("#pairTransactions")?.addEventListener("click", (event) => {
|
||||
const button = event.target.closest("[data-trace]");
|
||||
if (button) openTrace(Number(button.dataset.trace));
|
||||
});
|
||||
$("#traceClose")?.addEventListener("click", () => $("#traceModal")?.classList.remove("open"));
|
||||
$("#traceOk")?.addEventListener("click", () => $("#traceModal")?.classList.remove("open"));
|
||||
$("#traceModal")?.addEventListener("click", (event) => { if (event.target === event.currentTarget) event.currentTarget.classList.remove("open"); });
|
||||
}
|
||||
|
||||
function renderCompanyManualRecords() {
|
||||
@@ -712,8 +766,6 @@ function appendAdminReviewRow(record, kind) {
|
||||
const tbody = $("#auditRows");
|
||||
if (!tbody) return;
|
||||
const isAccount = kind === "account";
|
||||
// Account rows come from the server (full number visible only in this
|
||||
// authorized admin view); manual records are still browser-local demo data.
|
||||
const statusLabel = isAccount ? accountStatusLabel(record.status) : record.status;
|
||||
const row = document.createElement("tr");
|
||||
row.dataset.storedReview = record.id;
|
||||
@@ -726,48 +778,52 @@ function appendAdminReviewRow(record, kind) {
|
||||
? "公司提交资料、开户行、账号、账户类型与启用日期"
|
||||
: "公司手工记录、关联银行流水号、证明附件与提交说明";
|
||||
if (statusLabel !== "待复核" && statusLabel !== "待总账复核") row.dataset.resolved = "true";
|
||||
if (isAccount) row.dataset.accountStatus = record.status;
|
||||
|
||||
const riskCell = document.createElement("td");
|
||||
const risk = document.createElement("span"); risk.className = "task-level warning"; risk.textContent = "中"; riskCell.append(risk);
|
||||
riskCell.innerHTML = '<span class="pill pill-warn">中</span>';
|
||||
|
||||
const identityCell = document.createElement("td");
|
||||
const identity = document.createElement("strong");
|
||||
const detail = document.createElement("small");
|
||||
const typeCell = document.createElement("td");
|
||||
const periodCell = document.createElement("td");
|
||||
const impactCell = document.createElement("td");
|
||||
const identity = document.createElement("span"); identity.className = "cell-main";
|
||||
const detail = document.createElement("span"); detail.className = "cell-sub";
|
||||
if (isAccount) {
|
||||
identity.textContent = `${record.company_name} · ${record.bank_name} ${String(record.account_number).slice(-4)}`;
|
||||
detail.textContent = `${record.account_type} · 完整账号 ${record.account_number}`;
|
||||
typeCell.textContent = "账户登记";
|
||||
periodCell.textContent = record.effective_from || "待审核确定";
|
||||
impactCell.textContent = "账户识别与流水上传";
|
||||
} else {
|
||||
identity.textContent = `${record.company} · ${record.id}`;
|
||||
detail.textContent = `${record.direction} ${formatCurrency(record.amount)} 元 · ${record.counterparty} · ${record.subject}`;
|
||||
typeCell.textContent = "手工记录";
|
||||
periodCell.textContent = record.transactionDate;
|
||||
impactCell.textContent = `待确认往来 ${(Number(record.amount) / 10000).toFixed(2)} 万元`;
|
||||
}
|
||||
identityCell.append(identity, detail);
|
||||
|
||||
const typeCell = document.createElement("td");
|
||||
typeCell.textContent = isAccount ? "账户登记" : "手工记录";
|
||||
|
||||
const periodCell = document.createElement("td");
|
||||
periodCell.textContent = isAccount ? (record.effective_from || "待审核确定") : record.transactionDate;
|
||||
|
||||
const impactCell = document.createElement("td");
|
||||
impactCell.className = "wrap";
|
||||
impactCell.textContent = isAccount ? "账户识别与流水上传" : `待确认往来 ${(Number(record.amount) / 10000).toFixed(2)} 万元`;
|
||||
|
||||
const statusCell = document.createElement("td");
|
||||
const status = recordStatus(statusLabel);
|
||||
const badge = document.createElement("span"); badge.className = `status ${status.className}`; badge.textContent = status.label; statusCell.append(badge);
|
||||
statusCell.innerHTML = `<span class="pill ${pillClass(status.className)}">${status.label}</span>`;
|
||||
|
||||
const actionCell = document.createElement("td");
|
||||
const action = document.createElement("button"); action.className = "text-button"; action.dataset.auditAction = "";
|
||||
if (isAccount) row.dataset.accountStatus = record.status;
|
||||
action.textContent = row.dataset.resolved
|
||||
? (isAccount && record.status === "active" ? "管理" : "查看记录")
|
||||
: "复核";
|
||||
if (row.dataset.resolved) {
|
||||
const decisionLabels = { active: "复核通过并启用账户", returned: "退回公司修改", disabled: "停用并驳回" };
|
||||
const decisionLabel = isAccount ? decisionLabels[record.status] || statusLabel : record.decision || record.status;
|
||||
const reasonText = (isAccount ? record.review_reason : record.reviewReason) || "已留痕";
|
||||
action.dataset.record = `${decisionLabel} · ${reasonText}`;
|
||||
action.dataset.decision = decisionLabel;
|
||||
action.dataset.reason = reasonText;
|
||||
action.dataset.processedAt = (isAccount ? record.reviewed_at : record.reviewedAt) || "时间未记录";
|
||||
const isActiveAccount = isAccount && record.status === "active";
|
||||
if (isActiveAccount) {
|
||||
actionCell.innerHTML = '<button type="button" class="btn btn-sm btn-danger" data-audit-action="disable">停用</button>';
|
||||
} else {
|
||||
const resolvedLabel = isAccount
|
||||
? ({ returned: "已退回", disabled: "已停用" })[record.status] || "已通过"
|
||||
: (statusLabel === "已确认" ? "已通过" : "已驳回");
|
||||
actionCell.innerHTML = `<span class="meta">${resolvedLabel} · 系统管理员</span>`;
|
||||
}
|
||||
} else {
|
||||
actionCell.innerHTML = '<div class="row" style="gap:6px;"><button type="button" class="btn btn-sm btn-primary" data-audit-action="approve">通过</button><button type="button" class="btn btn-sm btn-danger" data-audit-action="reject">驳回</button></div>';
|
||||
}
|
||||
actionCell.append(action);
|
||||
|
||||
row.append(riskCell, identityCell, typeCell, periodCell, impactCell, statusCell, actionCell);
|
||||
tbody.append(row);
|
||||
}
|
||||
@@ -781,6 +837,7 @@ async function renderAdminAccountReviews() {
|
||||
const result = await response.json().catch(() => null);
|
||||
(result?.accounts || []).forEach((account) => appendAdminReviewRow(account, "account"));
|
||||
updateAuditCounts();
|
||||
updatePendingAccountNotice();
|
||||
}
|
||||
|
||||
function renderStoredAdminReviews() {
|
||||
@@ -805,10 +862,18 @@ function updateAuditCounts() {
|
||||
$$('[data-audit-filter]').forEach((button) => {
|
||||
const type = button.dataset.auditFilter;
|
||||
const count = unresolved.filter((row) => type === "all" || row.dataset.auditType === type).length;
|
||||
button.textContent = `${button.dataset.label} ${count}`;
|
||||
const badge = $(".tab-count", button);
|
||||
if (badge) badge.textContent = count;
|
||||
});
|
||||
const badge = $('.side-nav a[data-view="audit"] .nav-badge');
|
||||
if (badge) badge.textContent = unresolved.length;
|
||||
if (badge) {
|
||||
badge.textContent = unresolved.length;
|
||||
badge.style.display = unresolved.length ? "" : "none";
|
||||
}
|
||||
const pending = $("#pending-count");
|
||||
if (pending) pending.textContent = unresolved.length;
|
||||
const foot = $("#auditFoot");
|
||||
if (foot) foot.textContent = `共 ${rows.length} 项 · 待审核 ${unresolved.length} 项`;
|
||||
}
|
||||
|
||||
function companyStatusBadge(status) {
|
||||
@@ -820,24 +885,42 @@ function companyStatusBadge(status) {
|
||||
function renderAdminCompanyTable(companies) {
|
||||
const tbody = $("#companyTable tbody");
|
||||
if (!tbody) return;
|
||||
state.companies = companies;
|
||||
tbody.replaceChildren(...companies.map((company) => {
|
||||
const row = document.createElement("tr");
|
||||
row.dataset.companyId = company.id;
|
||||
const nameCell = document.createElement("td");
|
||||
const name = document.createElement("strong"); name.textContent = company.name;
|
||||
const code = document.createElement("small"); code.textContent = `COMP-${String(company.id).padStart(3, "0")}`;
|
||||
const name = document.createElement("span"); name.className = "cell-main"; name.textContent = company.name;
|
||||
const code = document.createElement("span"); code.className = "cell-sub"; code.textContent = company.credit_code || "统一社会信用代码待补充";
|
||||
nameCell.append(name, code);
|
||||
const credit = document.createElement("td"); credit.textContent = company.credit_code || "待补充";
|
||||
const accounts = document.createElement("td"); accounts.textContent = `${company.account_count ?? 0} 个`;
|
||||
const usernames = document.createElement("td"); usernames.textContent = company.usernames || "未创建";
|
||||
const accounts = document.createElement("td"); accounts.className = "num-col"; accounts.textContent = `${company.account_count ?? 0}`;
|
||||
const usernames = document.createElement("td"); usernames.className = "num"; usernames.textContent = company.usernames || "未创建";
|
||||
const cashier = document.createElement("td"); cashier.textContent = company.cashier_name || "未指定";
|
||||
const statusCell = document.createElement("td");
|
||||
const badge = companyStatusBadge(company.status);
|
||||
statusCell.innerHTML = `<span class="status ${badge.className}">${badge.label}</span>`;
|
||||
statusCell.innerHTML = `<span class="pill ${pillClass(badge.className)}">${badge.label}</span>`;
|
||||
const actionCell = document.createElement("td");
|
||||
actionCell.innerHTML = `<button class="text-button" data-toast="已打开 ${company.name} 主档">管理</button>`;
|
||||
row.append(nameCell, credit, accounts, usernames, cashier, statusCell, actionCell);
|
||||
actionCell.innerHTML = `<button type="button" class="btn btn-sm" data-company-view="${company.id}">查看</button>`;
|
||||
row.append(nameCell, accounts, usernames, cashier, statusCell, actionCell);
|
||||
return row;
|
||||
}));
|
||||
const foot = $("#companyFoot");
|
||||
if (foot) foot.textContent = `共 ${companies.length} 家公司`;
|
||||
}
|
||||
|
||||
function updatePendingAccountNotice() {
|
||||
const rows = $$('#auditRows tr[data-record-kind="account"]');
|
||||
const pending = rows.filter((r) => r.dataset.resolved !== "true");
|
||||
const notice = $("#notice-pending-account");
|
||||
if (!notice) return;
|
||||
if (pending.length) {
|
||||
notice.style.display = "";
|
||||
$("#pending-account-count").textContent = pending.length;
|
||||
const names = [...new Set(pending.map((r) => r.dataset.company))].join("、");
|
||||
$("#pending-account-body").textContent = (names || "成员公司") + " 提交了新银行账户登记,等待审核通过后纳入账期流水归集范围。";
|
||||
} else {
|
||||
notice.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
function setSelectOptions(select, names, { keepFirst = false } = {}) {
|
||||
@@ -858,7 +941,21 @@ function fillCompanySelects(names) {
|
||||
});
|
||||
setSelectOptions($("#auditCompany"), names, { keepFirst: true });
|
||||
setSelectOptions($("#flowCompany"), names, { keepFirst: true });
|
||||
setSelectOptions($('#reminderForm [name="company"]'), names, { keepFirst: true });
|
||||
const reminderList = $("#reminderCompanyList");
|
||||
if (reminderList) {
|
||||
reminderList.replaceChildren(...names.map((name) => {
|
||||
const label = document.createElement("label");
|
||||
label.className = "row";
|
||||
label.style.cssText = "gap:6px;font-size:13px;";
|
||||
const input = document.createElement("input");
|
||||
input.type = "checkbox";
|
||||
input.name = "company";
|
||||
input.value = name;
|
||||
input.style.width = "auto";
|
||||
label.append(input, document.createTextNode(name));
|
||||
return label;
|
||||
}));
|
||||
}
|
||||
setSelectOptions($('#openingDialog [name="from"]'), names);
|
||||
setSelectOptions($('#openingDialog [name="to"]'), names);
|
||||
}
|
||||
@@ -870,6 +967,7 @@ async function loadAdminCompanies() {
|
||||
const companies = result?.companies || [];
|
||||
renderAdminCompanyTable(companies);
|
||||
fillCompanySelects(companies.map((company) => company.name));
|
||||
if (companies.length >= 2) setPair(companies[0].name, companies[1].name);
|
||||
}
|
||||
|
||||
function initAdmin() {
|
||||
@@ -895,63 +993,23 @@ function initAdmin() {
|
||||
activeAuditType = button.dataset.auditFilter;
|
||||
$$("[data-audit-filter]").forEach((item) => {
|
||||
const active = item === button;
|
||||
item.classList.toggle("is-active", active);
|
||||
item.classList.toggle("active", active);
|
||||
item.setAttribute("aria-pressed", String(active));
|
||||
});
|
||||
filterAuditRows();
|
||||
}));
|
||||
$("#auditCompany")?.addEventListener("change", filterAuditRows);
|
||||
|
||||
const auditDialog = $("#auditDialog");
|
||||
$$('[data-close-audit]').forEach((button) => button.addEventListener("click", () => auditDialog.close()));
|
||||
// Delegated: account review rows arrive asynchronously from the API.
|
||||
$("#auditRows")?.addEventListener("click", (event) => {
|
||||
const button = event.target.closest("[data-audit-action]");
|
||||
if (!button) return;
|
||||
const row = button.closest("tr");
|
||||
state.auditRow = row;
|
||||
const cells = $$('td', row);
|
||||
const form = $("#auditForm");
|
||||
const decision = $('[name="decision"]', form);
|
||||
const reason = $('[name="reason"]', form);
|
||||
const submit = $('button[type="submit"]', form);
|
||||
form.reset();
|
||||
const isActiveAccount = row.dataset.recordKind === "account" && row.dataset.accountStatus === "active";
|
||||
const decisions = row.dataset.recordKind === "account"
|
||||
? (isActiveAccount ? ["停用并驳回"] : ["复核通过并启用账户", "退回公司修改", "停用并驳回"])
|
||||
: ["确认并纳入计算", "退回公司补充材料", "转为异常待后续处理"];
|
||||
decision.replaceChildren(new Option("请选择", ""), ...decisions.map((item) => new Option(item, item)));
|
||||
decision.disabled = false;
|
||||
reason.disabled = false;
|
||||
submit.hidden = false;
|
||||
$("#auditDialogTitle").textContent = `${cells[2].innerText.trim()} · ${cells[1].querySelector("strong").textContent}`;
|
||||
$("#auditDialogMeta").textContent = `${cells[3].innerText.trim()} · 影响 ${cells[4].innerText.trim()}`;
|
||||
const evidence = $("#auditEvidence");
|
||||
evidence.replaceChildren();
|
||||
const heading = document.createElement("strong"); heading.textContent = cells[1].querySelector("strong").textContent;
|
||||
const detail = document.createElement("small"); detail.textContent = cells[1].querySelector("small").textContent;
|
||||
const source = document.createElement("small"); source.textContent = `证据:${row.dataset.evidence || "银行原始行、导入批次、账户覆盖区间与公司说明"}`;
|
||||
evidence.append(heading, detail, source);
|
||||
if (button.dataset.record && !isActiveAccount) {
|
||||
decision.value = button.dataset.decision;
|
||||
reason.value = button.dataset.reason;
|
||||
decision.disabled = true;
|
||||
reason.disabled = true;
|
||||
submit.hidden = true;
|
||||
$("#auditDialogMeta").textContent = `已处理 · ${button.dataset.processedAt} · 系统管理员`;
|
||||
const record = document.createElement("small");
|
||||
record.textContent = `处理记录:${button.dataset.record}`;
|
||||
evidence.append(record);
|
||||
}
|
||||
auditDialog.showModal();
|
||||
});
|
||||
$("#auditForm")?.addEventListener("submit", async (event) => {
|
||||
event.preventDefault();
|
||||
const data = new FormData(event.currentTarget);
|
||||
const row = state.auditRow;
|
||||
const decision = String(data.get("decision"));
|
||||
const reason = String(data.get("reason"));
|
||||
const approved = decision.includes("通过") || decision.includes("确认并纳入");
|
||||
function openModal(id) { $("#" + id)?.classList.add("open"); }
|
||||
function closeModal(id) { $("#" + id)?.classList.remove("open"); }
|
||||
document.querySelectorAll("[data-close]").forEach((el) => el.addEventListener("click", () => closeModal(el.dataset.close)));
|
||||
document.querySelectorAll(".modal-backdrop").forEach((bd) => bd.addEventListener("click", (e) => { if (e.target === bd) bd.classList.remove("open"); }));
|
||||
document.addEventListener("keydown", (e) => { if (e.key === "Escape") document.querySelectorAll(".modal-backdrop.open").forEach((m) => m.classList.remove("open")); });
|
||||
|
||||
async function submitAuditResult(row) {
|
||||
const decision = state.auditDecision;
|
||||
const reason = state.auditReason || "";
|
||||
const approved = decision.includes("通过") || decision.includes("确认并纳入") || decision.includes("启用");
|
||||
const returned = decision.includes("退回");
|
||||
let storedStatus;
|
||||
let reviewedAccount = null;
|
||||
@@ -982,27 +1040,66 @@ function initAdmin() {
|
||||
}
|
||||
const status = recordStatus(storedStatus);
|
||||
const statusCell = row.children[5];
|
||||
statusCell.innerHTML = `<span class="status ${status.className}">${status.label}</span>`;
|
||||
statusCell.innerHTML = `<span class="pill ${pillClass(status.className)}">${status.label}</span>`;
|
||||
row.dataset.resolved = "true";
|
||||
const action = $("[data-audit-action]", row);
|
||||
if (reviewedAccount) {
|
||||
row.dataset.accountStatus = reviewedAccount.status;
|
||||
action.textContent = reviewedAccount.status === "active" ? "管理" : "查看记录";
|
||||
if (reviewedAccount) row.dataset.accountStatus = reviewedAccount.status;
|
||||
const actionCell = row.children[6];
|
||||
if (reviewedAccount && reviewedAccount.status === "active") {
|
||||
actionCell.innerHTML = '<button type="button" class="btn btn-sm btn-danger" data-audit-action="disable">停用</button>';
|
||||
} else {
|
||||
action.textContent = "查看记录";
|
||||
actionCell.innerHTML = `<span class="meta">${approved ? "已通过" : "已驳回"} · 系统管理员</span>`;
|
||||
}
|
||||
action.dataset.record = `${decision} · ${data.get("reason")}`;
|
||||
action.dataset.decision = decision;
|
||||
action.dataset.reason = data.get("reason");
|
||||
action.dataset.processedAt = new Date().toLocaleString("zh-CN", { hour12: false });
|
||||
updateAuditCounts();
|
||||
auditDialog.close();
|
||||
event.currentTarget.reset();
|
||||
showToast("审核结果已记录", approved ? "已通过的数据取得相应使用或核算资格" : "当前记录不参与自动核算");
|
||||
}
|
||||
|
||||
$("#auditRows")?.addEventListener("click", (event) => {
|
||||
const button = event.target.closest("[data-audit-action]");
|
||||
if (!button) return;
|
||||
const row = button.closest("tr");
|
||||
state.auditRow = row;
|
||||
const cells = $$("td", row);
|
||||
const company = cells[1]?.querySelector(".cell-main")?.textContent.trim() || "";
|
||||
const period = cells[3]?.textContent.trim() || "";
|
||||
const type = cells[2]?.textContent.trim() || "";
|
||||
const basis = row.dataset.evidence || "银行原始行、导入批次、账户覆盖区间与公司说明";
|
||||
const action = button.dataset.auditAction;
|
||||
if (action === "approve") {
|
||||
$("#approve-company").textContent = company;
|
||||
$("#approve-period").textContent = period;
|
||||
$("#approve-type").textContent = type;
|
||||
$("#approve-basis").textContent = basis;
|
||||
openModal("modal-approve");
|
||||
} else if (action === "reject") {
|
||||
$("#reject-reason").value = "";
|
||||
$("#reject-hint").style.display = "none";
|
||||
openModal("modal-reject");
|
||||
} else if (action === "disable") {
|
||||
state.auditDecision = "停用并驳回";
|
||||
state.auditReason = "停用并驳回该账户";
|
||||
submitAuditResult(row);
|
||||
}
|
||||
});
|
||||
|
||||
const dialog = $("#companyDialog");
|
||||
$("#openCompanyDialog")?.addEventListener("click", () => dialog.showModal());
|
||||
$("#approve-confirm")?.addEventListener("click", () => {
|
||||
if (!state.auditRow) return;
|
||||
closeModal("modal-approve");
|
||||
state.auditDecision = state.auditRow.dataset.recordKind === "account" ? "复核通过并启用账户" : "确认并纳入计算";
|
||||
state.auditReason = state.auditDecision;
|
||||
submitAuditResult(state.auditRow);
|
||||
});
|
||||
|
||||
$("#reject-confirm")?.addEventListener("click", () => {
|
||||
if (!state.auditRow) return;
|
||||
const reason = $("#reject-reason").value.trim();
|
||||
if (reason.length < 5) { $("#reject-hint").style.display = ""; return; }
|
||||
closeModal("modal-reject");
|
||||
state.auditDecision = state.auditRow.dataset.recordKind === "account" ? "退回公司修改" : "退回公司补充材料";
|
||||
state.auditReason = reason;
|
||||
submitAuditResult(state.auditRow);
|
||||
});
|
||||
|
||||
$("#openCompanyDialog")?.addEventListener("click", () => openModal("companyDialog"));
|
||||
$("#companyForm")?.addEventListener("submit", async (event) => {
|
||||
event.preventDefault();
|
||||
const data = new FormData(event.currentTarget);
|
||||
@@ -1032,7 +1129,7 @@ function initAdmin() {
|
||||
return;
|
||||
}
|
||||
const accountCreated = Boolean(result.username);
|
||||
dialog.close();
|
||||
closeModal("companyDialog");
|
||||
event.currentTarget.reset();
|
||||
await loadAdminCompanies();
|
||||
showToast(
|
||||
@@ -1041,47 +1138,121 @@ function initAdmin() {
|
||||
);
|
||||
});
|
||||
|
||||
function openCompanyDetail(companyId) {
|
||||
const company = (state.companies || []).find((c) => String(c.id) === String(companyId));
|
||||
if (!company) return;
|
||||
$("#d-title").textContent = company.name;
|
||||
$("#d-sub").textContent = `统一社会信用代码 ${company.credit_code || "待补充"}`;
|
||||
$("#d-kv").innerHTML =
|
||||
'<dt>公司名称</dt><dd>' + company.name + '</dd>' +
|
||||
'<dt>统一社会信用代码</dt><dd>' + (company.credit_code || "待补充") + '</dd>' +
|
||||
'<dt>出纳</dt><dd>' + (company.cashier_name || "未指定") + '</dd>' +
|
||||
'<dt>银行账户</dt><dd>' + (company.account_count ?? 0) + ' 个</dd>' +
|
||||
'<dt>状态</dt><dd>' + companyStatusBadge(company.status).label + '</dd>';
|
||||
$("#d-login-account").textContent = company.usernames || "未创建公司账号";
|
||||
$("#d-login-meta").textContent = company.usernames ? "公司端登录账号" : "该公司尚未创建登录账号";
|
||||
const resetBtn = $("#btn-reset-pwd");
|
||||
resetBtn.disabled = !company.usernames;
|
||||
resetBtn.textContent = "重置密码";
|
||||
resetBtn.dataset.companyId = company.id;
|
||||
openModal("modal-detail");
|
||||
}
|
||||
|
||||
$("#companyTable tbody")?.addEventListener("click", (event) => {
|
||||
const btn = event.target.closest("[data-company-view]");
|
||||
if (btn) openCompanyDetail(btn.dataset.companyView);
|
||||
});
|
||||
|
||||
$("#btn-reset-pwd")?.addEventListener("click", async () => {
|
||||
const companyId = $("#btn-reset-pwd").dataset.companyId;
|
||||
const usersResponse = await fetch("/api/admin/users").catch(() => null);
|
||||
const usersResult = await usersResponse?.json().catch(() => ({}));
|
||||
const user = (usersResult?.users || []).find((u) => String(u.company_id) === String(companyId) && u.role === "company");
|
||||
if (!user) { showToast("重置失败", "该公司尚无公司登录账号"); return; }
|
||||
const response = await fetch(`/api/admin/users/${user.id}/reset-password`, { method: "POST" }).catch(() => null);
|
||||
if (response?.status === 401) { window.location.href = "index.html"; return; }
|
||||
const result = await response?.json().catch(() => ({}));
|
||||
if (!response || !response.ok) { showToast("重置失败", result?.message || "请稍后重试"); return; }
|
||||
$("#btn-reset-pwd").disabled = true;
|
||||
$("#btn-reset-pwd").textContent = "已重置";
|
||||
$("#d-login-meta").textContent = `临时密码已生成(仅此一次):${result.initial_password},首次登录必须修改`;
|
||||
showToast("密码已重置", "临时密码仅本次显示,首次登录必须修改");
|
||||
});
|
||||
|
||||
const remind = $("#cs-remind");
|
||||
const track = $("#cs-switch-track");
|
||||
const thumb = $("#cs-switch-thumb");
|
||||
const daysSel = $("#cs-remind-days");
|
||||
function renderSwitch() {
|
||||
if (!remind) return;
|
||||
track.style.background = remind.checked ? "var(--accent)" : "var(--fg-soft)";
|
||||
thumb.style.left = remind.checked ? "18px" : "2px";
|
||||
if (daysSel) daysSel.disabled = !remind.checked;
|
||||
}
|
||||
remind?.addEventListener("change", renderSwitch);
|
||||
renderSwitch();
|
||||
|
||||
$("#systemSettings")?.addEventListener("submit", (event) => {
|
||||
event.preventDefault();
|
||||
const day = parseInt($("#cs-day")?.value, 10);
|
||||
const tip = $("#cs-save-tip");
|
||||
if (day && (day < 1 || day > 28)) {
|
||||
if (tip) { tip.style.display = ""; tip.style.color = "var(--danger)"; tip.textContent = "结账日须为 1-28 之间的整数"; }
|
||||
return;
|
||||
}
|
||||
if (tip) { tip.style.display = ""; tip.style.color = "var(--success)"; tip.textContent = "已保存 · 立即生效"; }
|
||||
showToast("系统计算口径已保存", "正式系统将记录修改前后值与操作人");
|
||||
});
|
||||
|
||||
$("#runClosingCheck")?.addEventListener("click", () => {
|
||||
const unresolved = $$(".audit-table tbody tr").filter((row) => row.dataset.resolved !== "true" && !$(".status.success", row)).length;
|
||||
const unresolved = $$(".audit-table tbody tr").filter((row) => row.dataset.resolved !== "true").length;
|
||||
const blockNotice = $("#block-notice");
|
||||
if (unresolved) {
|
||||
if (blockNotice) blockNotice.style.display = "";
|
||||
$("#closingDescription").textContent = `2026 年 7 月 · 仍有 ${unresolved} 项审核事项未处理`;
|
||||
$("#closingStatus").className = "pill pill-danger";
|
||||
$("#closingStatus").textContent = "已阻断";
|
||||
$("#executeClosing").disabled = true;
|
||||
showToast("结账检查未通过", `仍有 ${unresolved} 项审核事项,已打开审核中心`);
|
||||
showView("audit");
|
||||
} else {
|
||||
$$("#closingPanel .is-blocked").forEach((item) => {
|
||||
item.classList.remove("is-blocked");
|
||||
$("use", item).setAttribute("href", "icons.svg#circle-check");
|
||||
$("small", item).textContent = "检查已通过";
|
||||
if (blockNotice) blockNotice.style.display = "none";
|
||||
$$("#closingPanel [data-closing-state]").forEach((item) => {
|
||||
item.className = "pill pill-success";
|
||||
item.textContent = "已通过";
|
||||
});
|
||||
$("#closingDescription").textContent = "2026 年 7 月 · 全部前置检查已通过";
|
||||
$("#closingStatus").className = "status success";
|
||||
$("#closingStatus").className = "pill pill-success";
|
||||
$("#closingStatus").textContent = "可结账";
|
||||
$("#executeClosing").disabled = false;
|
||||
$("#executeClosing").removeAttribute("title");
|
||||
showToast("结账检查通过", "执行结账按钮已解锁");
|
||||
}
|
||||
});
|
||||
const closingDialog = $("#closingDialog");
|
||||
$("#executeClosing")?.addEventListener("click", () => closingDialog.showModal());
|
||||
$$('[data-close-closing]').forEach((button) => button.addEventListener("click", () => closingDialog.close()));
|
||||
|
||||
$("#executeClosing")?.addEventListener("click", () => openModal("closingDialog"));
|
||||
$$("[data-close-closing]").forEach((button) => button.addEventListener("click", () => closeModal("closingDialog")));
|
||||
$("#closingForm")?.addEventListener("submit", (event) => {
|
||||
event.preventDefault();
|
||||
closingDialog.close();
|
||||
closeModal("closingDialog");
|
||||
$("#closingDescription").textContent = "2026 年 7 月 · 已完成集团结账";
|
||||
$("#closingStatus").className = "status success";
|
||||
$("#closingStatus").className = "pill pill-success";
|
||||
$("#closingStatus").textContent = "已结账";
|
||||
$("#closingHistory").textContent = `${new Date().toLocaleString("zh-CN", { hour12: false })} · 系统管理员执行 2026 年 7 月结账 · 已写入审计记录`;
|
||||
$("#runClosingCheck").disabled = true;
|
||||
$("#executeClosing").disabled = true;
|
||||
$("#executeClosing").textContent = "7 月已结账";
|
||||
const closedNotice = $("#closed-notice");
|
||||
if (closedNotice) closedNotice.style.display = "";
|
||||
const tlCurrent = $("#tl-current");
|
||||
tlCurrent?.classList.remove("current");
|
||||
tlCurrent?.classList.add("closed");
|
||||
$("#tl-current-state").textContent = "已结账";
|
||||
showToast("2026 年 7 月已完成结账", "本期结果已锁定,后续补录将进入重开流程");
|
||||
});
|
||||
const openingDialog = $("#openingDialog");
|
||||
$("#openOpeningDialog")?.addEventListener("click", () => openingDialog.showModal());
|
||||
$$('[data-close-opening]').forEach((button) => button.addEventListener("click", () => openingDialog.close()));
|
||||
|
||||
$("#openOpeningDialog")?.addEventListener("click", () => openModal("openingDialog"));
|
||||
$$("[data-close-opening]").forEach((button) => button.addEventListener("click", () => closeModal("openingDialog")));
|
||||
$("#openingForm")?.addEventListener("submit", (event) => {
|
||||
event.preventDefault();
|
||||
const data = new FormData(event.currentTarget);
|
||||
@@ -1090,36 +1261,191 @@ function initAdmin() {
|
||||
return;
|
||||
}
|
||||
const row = document.createElement("tr");
|
||||
[data.get("from"), data.get("to"), data.get("subject"), data.get("direction")].forEach((value) => {
|
||||
const cell = document.createElement("td"); cell.textContent = value; row.append(cell);
|
||||
});
|
||||
const amount = document.createElement("td"); amount.className = "number"; amount.textContent = Number(data.get("amount")).toLocaleString("zh-CN", {minimumFractionDigits:2}); row.append(amount);
|
||||
const date = document.createElement("td"); date.textContent = data.get("effectiveDate"); row.append(date);
|
||||
const status = document.createElement("td"); status.innerHTML = '<span class="status warning">待复核</span>'; row.append(status);
|
||||
const self = document.createElement("td"); self.className = "cell-main"; self.textContent = data.get("from"); row.append(self);
|
||||
const peer = document.createElement("td"); peer.textContent = data.get("to"); row.append(peer);
|
||||
const subject = document.createElement("td"); subject.textContent = data.get("subject"); row.append(subject);
|
||||
const direction = document.createElement("td"); direction.textContent = data.get("direction"); row.append(direction);
|
||||
const amount = document.createElement("td"); amount.className = "num-col"; amount.textContent = Number(data.get("amount")).toLocaleString("zh-CN", { minimumFractionDigits: 2 }); row.append(amount);
|
||||
const date = document.createElement("td"); date.className = "num"; date.textContent = data.get("effectiveDate"); row.append(date);
|
||||
const status = document.createElement("td"); status.innerHTML = '<span class="pill pill-warn">待复核</span>'; row.append(status);
|
||||
$("#openingRows").append(row);
|
||||
openingDialog.close();
|
||||
closeModal("openingDialog");
|
||||
event.currentTarget.reset();
|
||||
showToast("期初余额已提交复核", "正式系统将保留录入依据与操作人");
|
||||
});
|
||||
|
||||
$("#reminderForm")?.addEventListener("submit", (event) => {
|
||||
event.preventDefault();
|
||||
const data = new FormData(event.currentTarget);
|
||||
const article = document.createElement("article");
|
||||
article.innerHTML = '<span class="notification-icon warning"><svg><use href="icons.svg#bell"/></svg></span>';
|
||||
const content = document.createElement("span");
|
||||
const heading = document.createElement("strong");
|
||||
heading.textContent = `${data.get("company")} · ${data.get("message")}`;
|
||||
const meta = document.createElement("small");
|
||||
meta.textContent = `手动提醒 · 截止 ${data.get("dueDate")} · 刚刚`;
|
||||
content.append(heading, meta);
|
||||
const status = document.createElement("em");
|
||||
status.className = "status warning";
|
||||
status.textContent = "未读";
|
||||
article.append(content, status);
|
||||
$("#adminReminderList").prepend(article);
|
||||
event.currentTarget.reset();
|
||||
showToast("提醒已发送", "对方将在公司业务端收到站内通知");
|
||||
const form = event.currentTarget;
|
||||
const checked = Array.prototype.slice.call(form.querySelectorAll('input[name="company"]:checked'));
|
||||
const type = $("#reminder-type").value;
|
||||
const content = $("#reminder-content").value.trim();
|
||||
const deadline = $("#reminder-deadline").value;
|
||||
$("#company-error").style.display = checked.length ? "none" : "";
|
||||
$("#content-error").style.display = content ? "none" : "";
|
||||
if (!checked.length || !content) return;
|
||||
const companies = checked.map((c) => c.value).join("、");
|
||||
const summary = content.length > 46 ? content.slice(0, 46) + "…" : content;
|
||||
const row = document.createElement("tr");
|
||||
row.dataset.source = "manual";
|
||||
row.innerHTML =
|
||||
'<td class="cell-main">' + companies + '</td>' +
|
||||
'<td><span class="tag">' + type + '</span></td>' +
|
||||
'<td class="wrap">' + summary.replace(/</g, "<") + '</td>' +
|
||||
'<td class="meta">刚刚<span class="cell-sub">人工 · 系统管理员</span></td>' +
|
||||
'<td class="meta">' + (deadline || "—") + '</td>' +
|
||||
'<td><span class="pill pill-danger">未读</span></td>' +
|
||||
'<td><div class="row" style="gap:6px;"><button type="button" class="btn btn-sm act-remind">再提醒</button><button type="button" class="btn btn-sm btn-ghost act-history" data-company="' + checked[0].value + '">查看历史</button></div></td>';
|
||||
$("#reminder-tbody").insertBefore(row, $("#reminder-tbody").firstChild);
|
||||
refreshReminderCounts();
|
||||
form.querySelectorAll('input[name="company"]').forEach((c) => { c.checked = false; });
|
||||
$("#reminder-content").value = "请于截止日期前完成 2026 年 7 月银行流水上传与待确认事项处理。";
|
||||
$("#reminder-deadline").value = "2026-08-29";
|
||||
const sendHint = $("#send-hint");
|
||||
sendHint.style.display = "";
|
||||
sendHint.textContent = "已发送给 " + companies + ",共 " + checked.length + " 家公司。";
|
||||
clearTimeout(sendHint._t);
|
||||
sendHint._t = setTimeout(() => { sendHint.style.display = "none"; }, 4000);
|
||||
});
|
||||
|
||||
// 提醒历史 tabs / 计数
|
||||
function refreshReminderCounts() {
|
||||
const rows = $$("#reminder-tbody tr");
|
||||
let all = rows.length, sys = 0, man = 0;
|
||||
rows.forEach((r) => { if (r.dataset.source === "system") sys++; else man++; });
|
||||
$("#count-all").textContent = all;
|
||||
$("#count-system").textContent = sys;
|
||||
$("#count-manual").textContent = man;
|
||||
$("#table-foot-count").textContent = "共 " + all + " 条提醒记录";
|
||||
}
|
||||
$("#reminder-tabs")?.addEventListener("click", (event) => {
|
||||
const btn = event.target.closest("button[data-filter]");
|
||||
if (!btn) return;
|
||||
$("#reminder-tabs").querySelectorAll("button").forEach((b) => b.classList.remove("active"));
|
||||
btn.classList.add("active");
|
||||
const filter = btn.dataset.filter;
|
||||
$$("#reminder-tbody tr").forEach((r) => { r.style.display = (filter === "all" || r.dataset.source === filter) ? "" : "none"; });
|
||||
});
|
||||
|
||||
$("#reminder-tbody")?.addEventListener("click", (event) => {
|
||||
const remindBtn = event.target.closest(".act-remind");
|
||||
if (remindBtn) {
|
||||
const tr = remindBtn.closest("tr");
|
||||
const timeCell = tr.children[3];
|
||||
const sub = timeCell.querySelector(".cell-sub");
|
||||
timeCell.firstChild.textContent = "刚刚";
|
||||
if (sub) sub.textContent = sub.textContent.replace(/\s*·?\s*第 \d+ 次/, "") + " · 再提醒";
|
||||
else timeCell.insertAdjacentHTML("beforeend", '<span class="cell-sub">再提醒</span>');
|
||||
remindBtn.textContent = "已再提醒";
|
||||
remindBtn.disabled = true;
|
||||
return;
|
||||
}
|
||||
const historyBtn = event.target.closest(".act-history");
|
||||
if (historyBtn) openReminderHistory(historyBtn.dataset.company);
|
||||
});
|
||||
|
||||
const reminderHISTORIES = {
|
||||
"金牛农业": [
|
||||
{ time: "2026-08-18 09:02", src: "系统 · 第 2 次", type: "流水未提交", status: "danger", statusText: "未读", body: "贵公司 7 月全部银行账户流水尚未提交,距 7 月结账顺延截止日仅剩 11 天,请尽快上传。" },
|
||||
{ time: "2026-08-11 09:00", src: "系统 · 第 1 次", type: "流水未提交", status: "danger", statusText: "未读", body: "2026 年 7 月银行流水上传提醒:请于截止日前完成全部账户流水提交。" },
|
||||
],
|
||||
"金牛新能源": [
|
||||
{ time: "2026-08-15 09:00", src: "系统", type: "单边待确认", status: "warn", statusText: "处理中", body: "与金牛贸易 3 笔单边流水合计 ¥4,860,000.00 待确认,请选择对方银行流水佐证。" },
|
||||
],
|
||||
"金牛置业": [
|
||||
{ time: "2026-08-14 16:40", src: "人工 · 张维", type: "流水未提交", status: "warn", statusText: "处理中", body: "中行尾号 8821 账户 7 月 6—16 日流水断档,请补传该期间银行回单或对账单。" },
|
||||
],
|
||||
"金牛贸易": [
|
||||
{ time: "2026-08-15 09:00", src: "系统", type: "单边待确认", status: "success", statusText: "已完成", body: "与金牛新能源往来 3 笔单边流水待确认,请核对 7 月销售回款记录。" },
|
||||
],
|
||||
"金牛煤业": [
|
||||
{ time: "2026-08-12 09:00", src: "系统", type: "科目待确认", status: "warn", statusText: "处理中", body: "2 笔手工记录往来科目待确认(应收 / 其他应收),涉及煤炭运费结算。" },
|
||||
],
|
||||
"金牛物流": [
|
||||
{ time: "2026-08-10 11:25", src: "人工 · 张维", type: "科目待确认", status: "success", statusText: "已完成", body: "1 笔运费结算手工记录科目待确认(其他应付待判定),请于结账前完成。" },
|
||||
],
|
||||
};
|
||||
|
||||
function openReminderHistory(company) {
|
||||
$("#history-title").textContent = company + " · 提醒历史";
|
||||
$("#history-sub").textContent = "2026 年 7 月账期以来的全部触达记录,按时间倒序。";
|
||||
const list = $("#history-list");
|
||||
const items = reminderHISTORIES[company] || [];
|
||||
if (!items.length) {
|
||||
list.innerHTML = '<div class="empty"><div class="e-title">暂无历史记录</div>该公司在 7 月账期内尚未收到过提醒。</div>';
|
||||
} else {
|
||||
list.innerHTML = items.map((it) => {
|
||||
return '<div class="list-row">' +
|
||||
'<span class="tag">' + it.type + '</span>' +
|
||||
'<div class="lr-main"><div class="lr-title">' + it.body + '</div><div class="lr-sub"><span class="meta">' + it.time + ' · ' + it.src + '</span></div></div>' +
|
||||
'<div class="lr-side"><span class="pill pill-' + it.status + '">' + it.statusText + '</span></div></div>';
|
||||
}).join("");
|
||||
}
|
||||
openModal("history-modal");
|
||||
}
|
||||
$("#history-close")?.addEventListener("click", () => closeModal("history-modal"));
|
||||
$("#history-ok")?.addEventListener("click", () => closeModal("history-modal"));
|
||||
|
||||
refreshReminderCounts();
|
||||
}
|
||||
|
||||
const FLOW_DEMO = [
|
||||
{ date: "2026-07-01", company: "金牛煤业", bank: "工行", account: "3305", acctLabel: "工行 · 尾号 3305", dir: "收", dirPill: "pill-success", peer: "平顶山市恒源电力燃料有限公司", summary: "煤炭销售款(6 月结算)", serial: "ICBC202607010031825", status: "未归集", statusPill: "pill-muted", amount: "1,860,000.00", amtClass: "amt-in", detail: { bank: "工商银行", account: "河南金牛煤业有限公司 · 基本户 1702 0231 0900 8133 05", time: "2026-07-01 09:42:17", peer: "平顶山市恒源电力燃料有限公司", peerAcct: "工行平顶山分行 1702 0218 0902 6641 20", amount: "¥ 1,860,000.00(收)", status: "未归集", pair: "—(外部单位交易)", subject: "—", batch: "—", note: "对方为集团外客户,不进入内部往来归集,仅作银行流水留档。" } },
|
||||
{ date: "2026-07-03", company: "金牛新能源", bank: "工行", account: "6649", acctLabel: "工行 · 尾号 6649", dir: "付", dirPill: "pill-danger", peer: "河南金牛贸易有限公司", summary: "光伏支架材料款", serial: "ICBC202607030094417", status: "单边", statusPill: "pill-danger", amount: "1,620,000.00", amtClass: "amt-out", detail: { bank: "工商银行", account: "河南金牛新能源有限公司 · 一般户 1704 0512 0900 2266 49", time: "2026-07-03 14:08:52", peer: "河南金牛贸易有限公司", peerAcct: "农行郑州金水支行 1606 3301 0400 0220 8", amount: "¥ 1,620,000.00(付)", status: "单边", pair: "金牛新能源 ↔ 金牛贸易", subject: "其他应付(新能源侧)", batch: "UNI-2026-07-002", note: "贸易侧 7 月上报流水中未找到对应收款,已挂起为单边流水,待贸易侧补充银行凭证佐证。" } },
|
||||
{ date: "2026-07-03", company: "金牛置业", bank: "中行", account: "8821", acctLabel: "中行 · 尾号 8821", dir: "收", dirPill: "pill-success", peer: "郑州市商品房预售资金监管专户", summary: "商品房预售款(A 区 12 号楼)", serial: "BOC202607030552108", status: "未归集", statusPill: "pill-muted", amount: "4,150,000.00", amtClass: "amt-in", detail: { bank: "中国银行", account: "河南金牛置业有限公司 · 一般户 2546 0387 0200 8821", time: "2026-07-03 10:26:31", peer: "郑州市商品房预售资金监管专户", peerAcct: "中行郑州郑东新区支行 2546 1180 0200 3477", amount: "¥ 4,150,000.00(收)", status: "未归集", pair: "—(外部单位交易)", subject: "—", batch: "—", note: "预售监管资金划入,属对外经营收款,不参与集团内部往来归集。" } },
|
||||
{ date: "2026-07-05", company: "金牛煤业", bank: "工行", account: "3305", acctLabel: "工行 · 尾号 3305", dir: "付", dirPill: "pill-danger", peer: "河南金牛物流有限公司", summary: "矿区运输费(6 月)", serial: "ICBC202607050127663", status: "已归集", statusPill: "pill-success", amount: "1,240,000.00", amtClass: "amt-out", detail: { bank: "工商银行", account: "河南金牛煤业有限公司 · 基本户 1702 0231 0900 8133 05", time: "2026-07-05 11:15:09", peer: "河南金牛物流有限公司", peerAcct: "建行郑州经开区支行 4105 0167 8080 5562", amount: "¥ 1,240,000.00(付)", status: "已归集", pair: "金牛煤业 ↔ 金牛物流", subject: "应付(煤业侧)", batch: "JC-2026-07-014", note: "与物流侧建行尾号 5562 账户 07-05 收款流水双向匹配,金额一致。" } },
|
||||
{ date: "2026-07-05", company: "金牛物流", bank: "建行", account: "5562", acctLabel: "建行 · 尾号 5562", dir: "收", dirPill: "pill-success", peer: "河南金牛煤业有限公司", summary: "矿区运输费(6 月)", serial: "CCB202607050312940", status: "已归集", statusPill: "pill-success", amount: "1,240,000.00", amtClass: "amt-in", detail: { bank: "建设银行", account: "河南金牛物流有限公司 · 基本户 4105 0167 8080 5562", time: "2026-07-05 11:15:36", peer: "河南金牛煤业有限公司", peerAcct: "工行平顶山分行 1702 0231 0900 8133 05", amount: "¥ 1,240,000.00(收)", status: "已归集", pair: "金牛物流 ↔ 金牛煤业", subject: "应收(物流侧)", batch: "JC-2026-07-014", note: "与煤业侧工行尾号 3305 账户 07-05 付款流水双向匹配,金额一致。" } },
|
||||
{ date: "2026-07-08", company: "金牛煤业", bank: "工行", account: "3305", acctLabel: "工行 · 尾号 3305", dir: "收", dirPill: "pill-success", peer: "河南金牛置业有限公司", summary: "煤炭采购款(7 月)", serial: "ICBC202607080208554", status: "待确认", statusPill: "pill-warn", amount: "3,200,000.00", amtClass: "amt-in", detail: { bank: "工商银行", account: "河南金牛煤业有限公司 · 基本户 1702 0231 0900 8133 05", time: "2026-07-08 15:47:22", peer: "河南金牛置业有限公司", peerAcct: "中行郑州郑东新区支行 2546 0387 0200 8821", amount: "¥ 3,200,000.00(收)", status: "待确认", pair: "金牛煤业 ↔ 金牛置业", subject: "应收(煤业侧)", batch: "—(待归集)", note: "置业中行尾号 8821 账户 07-06 至 07-16 流水断档,对方付款凭证缺失,暂无法完成双边匹配,已列入审核中心高风险事项。" } },
|
||||
{ date: "2026-07-09", company: "金牛贸易", bank: "农行", account: "2208", acctLabel: "农行 · 尾号 2208", dir: "收", dirPill: "pill-success", peer: "洛阳建工集团有限公司", summary: "钢材销售款(6 月发货)", serial: "ABC202607090773261", status: "未归集", statusPill: "pill-muted", amount: "2,480,000.00", amtClass: "amt-in", detail: { bank: "农业银行", account: "河南金牛贸易有限公司 · 基本户 1606 3301 0400 0220 8", time: "2026-07-09 09:58:44", peer: "洛阳建工集团有限公司", peerAcct: "中行洛阳分行 2546 2201 0500 7915", amount: "¥ 2,480,000.00(收)", status: "未归集", pair: "—(外部单位交易)", subject: "—", batch: "—", note: "对外钢材销售回款,不参与集团内部往来归集。" } },
|
||||
{ date: "2026-07-11", company: "金牛新能源", bank: "工行", account: "6649", acctLabel: "工行 · 尾号 6649", dir: "付", dirPill: "pill-danger", peer: "河南金牛贸易有限公司", summary: "电缆及配电柜采购款", serial: "ICBC202607110158902", status: "单边", statusPill: "pill-danger", amount: "1,950,000.00", amtClass: "amt-out", detail: { bank: "工商银行", account: "河南金牛新能源有限公司 · 一般户 1704 0512 0900 2266 49", time: "2026-07-11 16:32:08", peer: "河南金牛贸易有限公司", peerAcct: "农行郑州金水支行 1606 3301 0400 0220 8", amount: "¥ 1,950,000.00(付)", status: "单边", pair: "金牛新能源 ↔ 金牛贸易", subject: "其他应付(新能源侧)", batch: "UNI-2026-07-005", note: "贸易侧无对应收款记录,单边挂起。新能源↔贸易本月累计 3 笔单边流水,合计 486 万元。" } },
|
||||
{ date: "2026-07-14", company: "金牛煤业", bank: "中行", account: "9916", acctLabel: "中行 · 尾号 9916", dir: "付", dirPill: "pill-danger", peer: "平顶山天安煤业设备租赁有限公司", summary: "综采设备租赁费(7 月)", serial: "BOC202607140416337", status: "未归集", statusPill: "pill-muted", amount: "920,000.00", amtClass: "amt-out", detail: { bank: "中国银行", account: "河南金牛煤业有限公司 · 一般户 2546 0387 1100 9916", time: "2026-07-14 10:11:57", peer: "平顶山天安煤业设备租赁有限公司", peerAcct: "建行平顶山分行 4105 0229 8080 1347", amount: "¥ 920,000.00(付)", status: "未归集", pair: "—(外部单位交易)", subject: "—", batch: "—", note: "综采设备月度租赁支出,对方为集团外供应商,不参与内部归集。" } },
|
||||
{ date: "2026-07-18", company: "金牛煤业", bank: "交行", account: "7710", acctLabel: "交行 · 尾号 7710", dir: "付", dirPill: "pill-danger", peer: "平顶山市安泰矿山设备有限公司", summary: "提升机大修款", serial: "BOCOM202607180062194", status: "待确认", statusPill: "pill-warn", amount: "685,400.00", amtClass: "amt-out", detail: { bank: "交通银行", account: "河南金牛煤业有限公司 · 一般户 4110 6120 0181 0077 10(账户待审核)", time: "2026-07-18 13:29:40", peer: "平顶山市安泰矿山设备有限公司", peerAcct: "工行平顶山分行 1702 0218 0902 9075 63", amount: "¥ 685,400.00(付)", status: "待确认", pair: "—(外部单位交易)", subject: "—", batch: "—", note: "付款账户(交行尾号 7710)为新开户,尚在账户审核流程中,流水暂挂待确认,审核通过后自动归档为外部交易。" } },
|
||||
{ date: "2026-07-21", company: "金牛置业", bank: "中行", account: "8821", acctLabel: "中行 · 尾号 8821", dir: "收", dirPill: "pill-success", peer: "郑州市商品房预售资金监管专户", summary: "商品房预售款(A 区 15 号楼)", serial: "BOC202607210588420", status: "未归集", statusPill: "pill-muted", amount: "3,780,000.00", amtClass: "amt-in", detail: { bank: "中国银行", account: "河南金牛置业有限公司 · 一般户 2546 0387 0200 8821", time: "2026-07-21 09:35:12", peer: "郑州市商品房预售资金监管专户", peerAcct: "中行郑州郑东新区支行 2546 1180 0200 3477", amount: "¥ 3,780,000.00(收)", status: "未归集", pair: "—(外部单位交易)", subject: "—", batch: "—", note: "预售监管资金划入。该账户 07-06 至 07-16 存在流水断档,本笔为断档后首笔入账。" } },
|
||||
{ date: "2026-07-22", company: "金牛物流", bank: "建行", account: "5562", acctLabel: "建行 · 尾号 5562", dir: "收", dirPill: "pill-success", peer: "河南金牛贸易有限公司", summary: "钢材干线运输费(6-7 月)", serial: "CCB202607220347815", status: "已归集", statusPill: "pill-success", amount: "462,800.00", amtClass: "amt-in", detail: { bank: "建设银行", account: "河南金牛物流有限公司 · 基本户 4105 0167 8080 5562", time: "2026-07-22 14:52:26", peer: "河南金牛贸易有限公司", peerAcct: "农行郑州金水支行 1606 3301 0400 0220 8", amount: "¥ 462,800.00(收)", status: "已归集", pair: "金牛物流 ↔ 金牛贸易", subject: "应收(物流侧)", batch: "JC-2026-07-021", note: "与贸易侧农行尾号 2208 账户 07-22 付款流水双向匹配,金额一致,已计入 7 月往来批次。" } },
|
||||
{ date: "2026-07-24", company: "金牛新能源", bank: "工行", account: "6649", acctLabel: "工行 · 尾号 6649", dir: "付", dirPill: "pill-danger", peer: "河南金牛贸易有限公司", summary: "组件辅材结算款", serial: "ICBC202607240221476", status: "单边", statusPill: "pill-danger", amount: "1,290,000.00", amtClass: "amt-out", detail: { bank: "工商银行", account: "河南金牛新能源有限公司 · 一般户 1704 0512 0900 2266 49", time: "2026-07-24 11:06:33", peer: "河南金牛贸易有限公司", peerAcct: "农行郑州金水支行 1606 3301 0400 0220 8", amount: "¥ 1,290,000.00(付)", status: "单边", pair: "金牛新能源 ↔ 金牛贸易", subject: "其他应付(新能源侧)", batch: "UNI-2026-07-009", note: "贸易侧无对应收款记录,单边挂起。新能源↔贸易本月累计 3 笔单边流水,合计 486 万元。" } },
|
||||
{ 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: "对外螺纹钢采购付款,不参与集团内部往来归集。" } },
|
||||
];
|
||||
|
||||
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}">
|
||||
<td class="num">${row.date}</td>
|
||||
<td class="cell-main">${row.company}</td>
|
||||
<td>${row.acctLabel}</td>
|
||||
<td><span class="pill ${row.dirPill}">${row.dir}</span></td>
|
||||
<td class="wrap">${row.peer}</td>
|
||||
<td class="wrap">${row.summary}</td>
|
||||
<td class="num">${row.serial}</td>
|
||||
<td><span class="pill ${row.statusPill}">${row.status}</span></td>
|
||||
<td class="num-col ${row.amtClass}">¥ ${row.amount}</td>
|
||||
</tr>`;
|
||||
}).join("");
|
||||
const count = $("#flowCount");
|
||||
if (count) count.textContent = `共 ${FLOW_DEMO.length} 笔 · 本页 1-${FLOW_DEMO.length}`;
|
||||
}
|
||||
|
||||
function openFlowDetail(index) {
|
||||
const modal = $("#tx-modal");
|
||||
if (!modal) return;
|
||||
const row = FLOW_DEMO[index];
|
||||
if (!row) return;
|
||||
$("#tx-modal-sub").textContent = `${row.company} · ${row.acctLabel} · ${row.date}`;
|
||||
const d = row.detail;
|
||||
$("#d-serial").textContent = row.serial;
|
||||
$("#d-bank").textContent = d.bank;
|
||||
$("#d-account").textContent = d.account;
|
||||
$("#d-time").textContent = d.time;
|
||||
$("#d-peer").textContent = d.peer;
|
||||
$("#d-peer-acct").textContent = d.peerAcct;
|
||||
$("#d-amount").textContent = d.amount;
|
||||
$("#d-status").textContent = d.status;
|
||||
$("#d-pair").textContent = d.pair;
|
||||
$("#d-subject").textContent = d.subject;
|
||||
$("#d-batch").textContent = d.batch;
|
||||
$("#d-note").textContent = d.note;
|
||||
modal.classList.add("open");
|
||||
}
|
||||
|
||||
function visibleRows(table) {
|
||||
@@ -1141,26 +1467,29 @@ function filterFlows() {
|
||||
}
|
||||
let count = 0;
|
||||
$$("tbody tr", table).forEach((row) => {
|
||||
const rowDate = $("td", row).textContent.trim().replaceAll(".", "-");
|
||||
const rowDate = row.dataset.date || $("td", row).textContent.trim().replaceAll(".", "-");
|
||||
const matchesCompany = company === "全部公司" || row.dataset.company === company;
|
||||
const matchesBank = bank === "全部银行" || row.dataset.bank === bank;
|
||||
const matchesAccount = account === "全部账户" || row.textContent.includes(account);
|
||||
const matchesAccount = account === "全部账户" || row.dataset.account === account || (row.dataset.account === undefined && row.textContent.includes(account));
|
||||
const matchesDate = rowDate >= startDate && rowDate <= endDate;
|
||||
const matchesKeyword = !keyword || row.textContent.toLowerCase().includes(keyword);
|
||||
row.hidden = !(matchesCompany && matchesBank && matchesAccount && matchesDate && matchesKeyword);
|
||||
if (!row.hidden) count += 1;
|
||||
});
|
||||
$("#flowCount").textContent = count;
|
||||
$("#flowCount").textContent = count === 0 ? "共 0 条 · 无匹配记录" : `共 ${count} 笔 · 本页 1-${count}`;
|
||||
showToast("查询完成", `当前显示 ${count} 笔流水`);
|
||||
}
|
||||
|
||||
function exportFlows() {
|
||||
const table = $("#flowTable");
|
||||
const rows = visibleRows(table);
|
||||
const headers = ["交易日期", "公司", "银行及账号", "收付方向", "对方户名及账号", "摘要", "银行流水号", "归集状态", "金额", "导入批次", "源行定位"];
|
||||
const headers = ["交易日期", "公司", "银行账户", "方向", "对方户名", "摘要", "银行流水号", "归集状态", "金额", "导入批次", "源行定位"];
|
||||
const records = rows.map((row, index) => {
|
||||
const cells = $$('td', row).map((cell) => cell.innerText.replace(/\n/g, " ").trim());
|
||||
return [cells[0], portal === "company" ? "A公司" : cells[1].split(" ")[0], portal === "company" ? cells[1] : cells[1], ...cells.slice(2), `IMP-DEMO-${String(index + 1).padStart(3, "0")}`, `Sheet1!R${index + 8}`];
|
||||
const imp = `IMP-DEMO-${String(index + 1).padStart(3, "0")}`;
|
||||
const loc = `Sheet1!R${index + 8}`;
|
||||
if (portal === "admin") return [...cells.slice(0, 9), imp, loc];
|
||||
return [cells[0], "A公司", 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");
|
||||
@@ -1174,6 +1503,24 @@ function exportFlows() {
|
||||
function initFlowTools() {
|
||||
$("#applyFlowFilters")?.addEventListener("click", filterFlows);
|
||||
$("#exportFlows")?.addEventListener("click", exportFlows);
|
||||
if (portal !== "admin") return;
|
||||
renderFlowRows();
|
||||
$("#resetFlowFilters")?.addEventListener("click", () => {
|
||||
$("#flowCompany").value = "全部公司";
|
||||
$("#flowBank").value = "全部银行";
|
||||
$("#flowAccount").value = "全部账户";
|
||||
$("#flowStart").value = "2026-07-01";
|
||||
$("#flowEnd").value = "2026-07-31";
|
||||
$("#flowKeyword").value = "";
|
||||
filterFlows();
|
||||
});
|
||||
$("#flowTable tbody")?.addEventListener("click", (event) => {
|
||||
const row = event.target.closest("tr[data-flow-idx]");
|
||||
if (row) openFlowDetail(Number(row.dataset.flowIdx));
|
||||
});
|
||||
$("#tx-modal-close")?.addEventListener("click", () => $("#tx-modal")?.classList.remove("open"));
|
||||
$("#tx-modal-ok")?.addEventListener("click", () => $("#tx-modal")?.classList.remove("open"));
|
||||
$("#tx-modal")?.addEventListener("click", (event) => { if (event.target === event.currentTarget) event.currentTarget.classList.remove("open"); });
|
||||
}
|
||||
|
||||
function resetUpload() {
|
||||
|
||||
Reference in New Issue
Block a user