wwt-certificate-tool 1.2.36 → 1.2.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app.html +245 -79
- package/dist/version.json +6 -6
- package/package.json +1 -1
package/dist/app.html
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
|
-
<title>WWT 金蝶凭证工具 · 纯前端版 P1.2.
|
|
6
|
+
<title>WWT 金蝶凭证工具 · 纯前端版 P1.2.49</title>
|
|
7
7
|
<style>*{box-sizing:border-box;margin:0;padding:0}
|
|
8
8
|
body{font-family:"Microsoft YaHei","PingFang SC",-apple-system,Segoe UI,sans-serif;font-size:13px;color:#1f2328;background:#f4f6f9;line-height:1.6}
|
|
9
9
|
h1{font-size:17px;font-weight:600}
|
|
@@ -162,6 +162,7 @@ td.num{text-align:right;font-variant-numeric:tabular-nums}
|
|
|
162
162
|
td[contenteditable="true"]:focus{outline:2px solid #2f6feb;background:#fff}
|
|
163
163
|
tr.bad td{background:#fdf1f1}
|
|
164
164
|
tr.bad:hover td{background:#fbe9e9}
|
|
165
|
+
.dim-missing{color:#a40e26;font-weight:600;white-space:nowrap}
|
|
165
166
|
.tag{display:inline-block;padding:1px 6px;border-radius:9px;font-size:11px}
|
|
166
167
|
.tag.g{background:#eaf6ec;color:#116329}
|
|
167
168
|
.tag.r{background:#fdf1f1;color:#a40e26}
|
|
@@ -315,7 +316,7 @@ input[data-f="field"][list^="dlGe_"]::placeholder{color:#a8861f;font-style:itali
|
|
|
315
316
|
<div class="brand">
|
|
316
317
|
<span class="logo">WWT</span>
|
|
317
318
|
<div>
|
|
318
|
-
<h1>金蝶凭证工具 <span class="ver">P1.2.
|
|
319
|
+
<h1>金蝶凭证工具 <span class="ver">P1.2.49</span></h1>
|
|
319
320
|
<div class="sub">纯前端离线版 · 双击即用 · 数据只存在本机,不上传任何服务器</div>
|
|
320
321
|
</div>
|
|
321
322
|
</div>
|
|
@@ -348,7 +349,7 @@ input[data-f="field"][list^="dlGe_"]::placeholder{color:#a8861f;font-style:itali
|
|
|
348
349
|
<!-- ① 上传 -->
|
|
349
350
|
<section class="pane on" id="tab-upload">
|
|
350
351
|
<div class="notice">
|
|
351
|
-
<b>第一次用请按顺序来:</b>先传 <b>9 个基础数据表</b>(一次传完,以后不用再传)→ 再传 <b>本月
|
|
352
|
+
<b>第一次用请按顺序来:</b>先传 <b>9 个基础数据表</b>(一次传完,以后不用再传)→ 再传 <b>本月 9 个业务数据表</b> → 去「② 映射配置」改月份 → 「④ 生成凭证」→「⑦ 导出金蝶」。
|
|
352
353
|
<br><b>业务数据表必须是 .xls 原文件,不要改名、不要另存为 .xlsx。</b>
|
|
353
354
|
</div>
|
|
354
355
|
<div class="rowacts">
|
|
@@ -357,7 +358,7 @@ input[data-f="field"][list^="dlGe_"]::placeholder{color:#a8861f;font-style:itali
|
|
|
357
358
|
</div>
|
|
358
359
|
<h3>基础数据(9 个 · 一次上传长期有效)<span class="tip" style="margin-left:8px">支持 <b>点击按钮</b> 或 <b>把 Excel 文件拖到这里</b></span></h3>
|
|
359
360
|
<div class="grid dropzone" id="grpBase" data-kind="base"></div>
|
|
360
|
-
<h3>本月业务数据(
|
|
361
|
+
<h3>本月业务数据(9 个 · 每月重传)<span class="tip" style="margin-left:8px">支持 <b>点击按钮</b> 或 <b>把 .xls 文件拖到这里</b></span></h3>
|
|
361
362
|
<div class="grid dropzone" id="grpBiz" data-kind="biz"></div>
|
|
362
363
|
<div id="uploadLog" class="log"></div>
|
|
363
364
|
<input type="file" id="pickBase" multiple accept=".xls,.xlsx" style="display:none">
|
|
@@ -522,6 +523,8 @@ input[data-f="field"][list^="dlGe_"]::placeholder{color:#a8861f;font-style:itali
|
|
|
522
523
|
<datalist id="dlSubjects"></datalist>
|
|
523
524
|
<datalist id="dlAuxFixed"></datalist>
|
|
524
525
|
<datalist id="dlBizFields"></datalist>
|
|
526
|
+
<!-- 【P1.2.38】金额字段专用下拉:在业务字段基础上加上 __AUTO_DIFF__ 自动补差额等常量 -->
|
|
527
|
+
<datalist id="dlAmountFields"></datalist>
|
|
525
528
|
<datalist id="dlTransform"></datalist>
|
|
526
529
|
<datalist id="dlAuxValue"></datalist>
|
|
527
530
|
|
|
@@ -822,6 +825,7 @@ window.WWT_CONST = (function () {
|
|
|
822
825
|
"FOPPACCTNAME","FITEMNUM","FITEMNAME","FSUBITEMNUM","FSUBITEMNAME","FCURRENCYNUM","FCURRENCYNAME",
|
|
823
826
|
"FAMOUNTFOR","FAMOUNT"];
|
|
824
827
|
const MISSING_HEAD = ["来源单据","业务编号","科目","维度","名称"];
|
|
828
|
+
const SUBJ_MISS_HEAD = ["科目编码","科目名称","出现次数","涉及凭证号"];
|
|
825
829
|
|
|
826
830
|
/* ---------- 16 种辅助核算维度 ---------- */
|
|
827
831
|
// 草稿列名:[名称列, 编码列]
|
|
@@ -870,6 +874,8 @@ window.WWT_CONST = (function () {
|
|
|
870
874
|
"调拨未确认":{"file":"调拨未确认.xls","sheet":"调拨总","datecol":"出库日期","party":"单位名称","partytype":"客户","no":"出库单号"},
|
|
871
875
|
"在修车":{"file":"在修车.xls","sheet":"在修车","datecol":"进厂日期","party":"单位名称","partytype":"客户","no":"工单号"},
|
|
872
876
|
"已完工未结算":{"file":"已完工未结算.xls","sheet":"完工未结","datecol":"结算日期","party":"单位名称","partytype":"客户","no":"工单号"},
|
|
877
|
+
// 【P1.2.39】新增第 9 个业务源:维修已结算(按实际表头 维修已结算.xls 校准)
|
|
878
|
+
"维修已结算":{"file":"维修已结算.xls","sheet":"维修已结算","datecol":"结算日期","party":"单位名称","partytype":"客户","no":"工单号"},
|
|
873
879
|
};
|
|
874
880
|
const BIZ_SLOTS = [
|
|
875
881
|
{key:"入库已确认", label:"入库已确认(采购-已确认)"},
|
|
@@ -880,9 +886,13 @@ window.WWT_CONST = (function () {
|
|
|
880
886
|
{key:"调拨未确认", label:"调拨未确认"},
|
|
881
887
|
{key:"在修车", label:"在修车(维修在修)"},
|
|
882
888
|
{key:"已完工未结算", label:"已完工未结算(维修完工)"},
|
|
889
|
+
{key:"维修已结算", label:"维修已结算(维修已结)"},
|
|
883
890
|
];
|
|
884
891
|
// 对碰检测只覆盖"已确认/已结算"的来源;已完工未结算 用户要求不参与(2026-09-09)
|
|
885
|
-
|
|
892
|
+
// 【P1.2.39】新增 维修已结算(已结算类,按设计意图纳入对碰检测)
|
|
893
|
+
// 【P1.2.48】对碰检测增加「入库未结算」:用户要求付款方式=挂账的入库数据,
|
|
894
|
+
// 即使供应商尚未在基础供应商表建档,也要报出来,便于补档案/补映射。
|
|
895
|
+
const DETECT_ALLOWED_SOURCES = ["入库已确认","入库未结算","销售已确认","调拨已确认","维修已结算"];
|
|
886
896
|
// v1.2.20:只核对 挂账 / 对账S / 冲转S 三类(去掉 对帐F / 冲转F)
|
|
887
897
|
const DETECT_ALLOWED_PAY = ["对帐S","挂帐","冲转S"];
|
|
888
898
|
|
|
@@ -974,7 +984,7 @@ window.WWT_CONST = (function () {
|
|
|
974
984
|
const JOIN_PREFIX = ["客户","供应商","部门","员工","其他往来"];
|
|
975
985
|
|
|
976
986
|
return {
|
|
977
|
-
KINGDEE_COLUMNS, KINGDEE_NAMES, CASHFLOW_HEAD, MISSING_HEAD,
|
|
987
|
+
KINGDEE_COLUMNS, KINGDEE_NAMES, CASHFLOW_HEAD, MISSING_HEAD, SUBJ_MISS_HEAD,
|
|
978
988
|
DIM_DISPLAY, DIM_COLUMNS, DIM_LIST, STRICT_DIMS,
|
|
979
989
|
SOURCE_META, BIZ_SLOTS, BASE_SLOTS,
|
|
980
990
|
DETECT_ALLOWED_SOURCES, DETECT_ALLOWED_PAY,
|
|
@@ -2481,11 +2491,19 @@ window.WWT_ENGINE = (function () {
|
|
|
2481
2491
|
if (m && clean(m.whcode)) return { code: clean(m.whcode), name: clean(m.whname) || clean(m.whcode) };
|
|
2482
2492
|
return { code: "LJ", name: "LJ仓" };
|
|
2483
2493
|
}
|
|
2484
|
-
//
|
|
2494
|
+
// 维度反查:优先当前维度对应的基础表,再兜底其他表
|
|
2495
|
+
// 【P1.2.45】修复同名单位跨表串码:供应商维度先查供应商表,客户维度先查客户表。
|
|
2485
2496
|
function lookupDimCode(dim, name, idx) {
|
|
2486
2497
|
if (!name) return "";
|
|
2487
2498
|
const order = ["其他往来", "客户", "供应商", "部门", "员工", "费用项目"];
|
|
2499
|
+
// 1) 当前维度优先
|
|
2500
|
+
if (dim && idx.tables[dim]) {
|
|
2501
|
+
const rec = idx.tables[dim][name] || ciGetObj(idx.tables[dim], name);
|
|
2502
|
+
if (rec && rec.code) return rec.code;
|
|
2503
|
+
}
|
|
2504
|
+
// 2) 兜底:保持原六表顺序(兼容历史模糊匹配场景)
|
|
2488
2505
|
for (const d of order) {
|
|
2506
|
+
if (d === dim) continue; // 已查过
|
|
2489
2507
|
const t = idx.tables[d];
|
|
2490
2508
|
if (!t) continue;
|
|
2491
2509
|
const rec = t[name] || ciGetObj(t, name);
|
|
@@ -2525,15 +2543,27 @@ window.WWT_ENGINE = (function () {
|
|
|
2525
2543
|
code = lookupDimCode(d, name, idx) || lookupDimByField(d, vf, name, idx);
|
|
2526
2544
|
}
|
|
2527
2545
|
} else {
|
|
2528
|
-
|
|
2546
|
+
let raw = getField(row, vf, curSrc, idx);
|
|
2547
|
+
// 【P1.2.43】UI 的 valueField 下拉同时列出「业务源列名」和「基础表名称/编码(可当固定值用)」。
|
|
2548
|
+
// 当用户选了基础表里的固定名称/编码、且它不是当前业务行的列名时,直接当作固定值反查编码。
|
|
2549
|
+
let vfAsFixed = false;
|
|
2550
|
+
if (!clean(raw) && vf && vf.indexOf(".") < 0 && !Object.prototype.hasOwnProperty.call(row || {}, vf)) {
|
|
2551
|
+
raw = vf;
|
|
2552
|
+
vfAsFixed = true;
|
|
2553
|
+
}
|
|
2554
|
+
name = applyTransform(raw, aux.transform);
|
|
2529
2555
|
if (/\.编码$/.test(vf) && name) { code = name; }
|
|
2530
2556
|
if (name) {
|
|
2531
2557
|
if (d === "仓库") {
|
|
2532
2558
|
const w = lookupWh(name);
|
|
2533
2559
|
code = w.code; name = w.name;
|
|
2534
2560
|
} else if (d === "收入类型" || d === "成本类型") {
|
|
2535
|
-
// 硬约束:只认「出库单类 /
|
|
2536
|
-
|
|
2561
|
+
// 硬约束:只认「出库单类 / 单类」,盘亏清空,其它来源字段一律清空;
|
|
2562
|
+
// 【P1.2.41】维修已结算没有「单类」,允许用「收费类型」映射成本类型;
|
|
2563
|
+
// 【P1.2.42】同一例外扩展到 收入类型(收费类型 → 收入类型 也可走映射表)。
|
|
2564
|
+
const allowVf = (vf === "出库单类" || vf === "单类") ||
|
|
2565
|
+
(curSrc === "维修已结算" && vf === "收费类型");
|
|
2566
|
+
if (allowVf) {
|
|
2537
2567
|
if (name.indexOf("盘亏") >= 0) { name = ""; code = ""; }
|
|
2538
2568
|
else {
|
|
2539
2569
|
const r = lookupTypeDim(d, name, vf, idx);
|
|
@@ -2546,7 +2576,17 @@ window.WWT_ENGINE = (function () {
|
|
|
2546
2576
|
code = lookupDimCode(d, name, idx) || lookupDimByField(d, vf, name, idx);
|
|
2547
2577
|
if (!code) {
|
|
2548
2578
|
const t = idx.tables[d];
|
|
2549
|
-
if (t) {
|
|
2579
|
+
if (t) {
|
|
2580
|
+
const rec = t[name] || ciGetObj(t, name);
|
|
2581
|
+
if (rec && rec.code) code = rec.code;
|
|
2582
|
+
else if (vfAsFixed) {
|
|
2583
|
+
// 用户可能选了基础表编码当固定值,按编码反查名称
|
|
2584
|
+
for (const k in t) {
|
|
2585
|
+
if (k.indexOf("CODE::") === 0) continue;
|
|
2586
|
+
if (clean(t[k].code) === clean(name)) { code = t[k].code; name = t[k].name; break; }
|
|
2587
|
+
}
|
|
2588
|
+
}
|
|
2589
|
+
}
|
|
2550
2590
|
}
|
|
2551
2591
|
}
|
|
2552
2592
|
}
|
|
@@ -2563,6 +2603,16 @@ window.WWT_ENGINE = (function () {
|
|
|
2563
2603
|
}
|
|
2564
2604
|
out[d] = { name: name, code: code };
|
|
2565
2605
|
}
|
|
2606
|
+
|
|
2607
|
+
// 【P1.2.48】存货类科目(1403 开头)若规则未配置仓库维度,默认带出厂牌 LJ/零件(LJ仓),
|
|
2608
|
+
// 避免金蝶报「必要录维度未录入:仓库」。若用户已显式配置仓库维度(即使值空)则不覆盖。
|
|
2609
|
+
if (line && String(line.subject || "").startsWith("1403")) {
|
|
2610
|
+
const wh = out["仓库"];
|
|
2611
|
+
if (!wh || (!clean(wh.code) && !clean(wh.name))) {
|
|
2612
|
+
out["仓库"] = { name: "LJ仓", code: "LJ" };
|
|
2613
|
+
}
|
|
2614
|
+
}
|
|
2615
|
+
|
|
2566
2616
|
return out;
|
|
2567
2617
|
}
|
|
2568
2618
|
|
|
@@ -2611,7 +2661,7 @@ window.WWT_ENGINE = (function () {
|
|
|
2611
2661
|
function truncateBiz(s, sep) {
|
|
2612
2662
|
const arr = clean(s).split(sep).filter(Boolean);
|
|
2613
2663
|
if (arr.length <= 3) return arr.join(sep);
|
|
2614
|
-
return arr.slice(0, 3).join(sep) + sep + "
|
|
2664
|
+
return arr.slice(0, 3).join(sep) + sep + "等共" + arr.length + "张";
|
|
2615
2665
|
}
|
|
2616
2666
|
function buildSummaryCtx(items, rule, curSrc) {
|
|
2617
2667
|
const meta = C.SOURCE_META[curSrc] || {};
|
|
@@ -2760,16 +2810,28 @@ window.WWT_ENGINE = (function () {
|
|
|
2760
2810
|
|
|
2761
2811
|
// Step A:分桶(决定几张凭证)
|
|
2762
2812
|
const genMode = opts.modeOverride || rule.genMode || "一对一";
|
|
2813
|
+
const gb = opts.groupByOverride || rule.genGroupBy || "";
|
|
2763
2814
|
let buckets = [];
|
|
2764
2815
|
if (genMode === "一对一") {
|
|
2765
2816
|
buckets = rowsIn.map(it => [it]);
|
|
2766
2817
|
} else if (genMode === "全部汇总") {
|
|
2767
|
-
|
|
2818
|
+
// 【P1.2.38】全部汇总也可按分组字段拆证;若分组字段为空/默认单位名称,则保持原本「单张凭证汇总全部」行为
|
|
2819
|
+
if (gb && gb !== "单位名称") {
|
|
2820
|
+
const m = new Map();
|
|
2821
|
+
for (const it of rowsIn) {
|
|
2822
|
+
const v = clean(it.row[gb]) || "(空)";
|
|
2823
|
+
if (!m.has(v)) m.set(v, []);
|
|
2824
|
+
m.get(v).push(it);
|
|
2825
|
+
}
|
|
2826
|
+
buckets = Array.from(m.values());
|
|
2827
|
+
} else {
|
|
2828
|
+
buckets = [rowsIn];
|
|
2829
|
+
}
|
|
2768
2830
|
} else {
|
|
2769
|
-
const
|
|
2831
|
+
const useGb = gb || "单位名称";
|
|
2770
2832
|
const m = new Map();
|
|
2771
2833
|
for (const it of rowsIn) {
|
|
2772
|
-
const v = clean(it.row[
|
|
2834
|
+
const v = clean(it.row[useGb]) || "(空)";
|
|
2773
2835
|
if (!m.has(v)) m.set(v, []);
|
|
2774
2836
|
m.get(v).push(it);
|
|
2775
2837
|
}
|
|
@@ -2785,11 +2847,15 @@ window.WWT_ENGINE = (function () {
|
|
|
2785
2847
|
stats.vouchers++;
|
|
2786
2848
|
|
|
2787
2849
|
// Step B:sub_bucket = group_key值 × 分支lines
|
|
2788
|
-
|
|
2850
|
+
// 【P1.2.40】全部汇总且按非单位名称字段拆证时,取消子桶按单位名称细分,实现真正的按该字段汇总
|
|
2851
|
+
// 【P1.2.46】若规则配置了「拆分字段」,子桶按拆分字段拆分,不再按单位名称细分(实现 1 证内按供应商等字段分录)
|
|
2852
|
+
const groupKeyField = (genMode === "一对一" || splitFs.length > 0) ? null :
|
|
2853
|
+
(genMode === "全部汇总" && gb && gb !== "单位名称") ? null : "单位名称";
|
|
2789
2854
|
const subMap = new Map();
|
|
2790
2855
|
for (const it of bucket) {
|
|
2791
2856
|
const gv = groupKeyField ? (clean(it.row[groupKeyField]) || "(空)") : "";
|
|
2792
|
-
const
|
|
2857
|
+
const splitT = splitFs.map(f => clean(it.row[f]));
|
|
2858
|
+
const k = JSON.stringify([gv, splitT, linesKey(it._lines)]);
|
|
2793
2859
|
if (!subMap.has(k)) subMap.set(k, { gv: gv, lines: it._lines, items: [], acct: {} });
|
|
2794
2860
|
subMap.get(k).items.push(it);
|
|
2795
2861
|
}
|
|
@@ -2905,8 +2971,18 @@ window.WWT_ENGINE = (function () {
|
|
|
2905
2971
|
* covered:被规则命中(candidate→产出凭证)
|
|
2906
2972
|
* candidate_missed:满足某条规则全部 = 条件、但未生成凭证(一般 = 缺编码/客户未匹配);P1.2.15 起 generate() 主循环无此中间态,固定 0
|
|
2907
2973
|
* excluded:既不满足任一规则的 = 条件,也未被命中(设计排除——例:本场景对账S规则不接 冲转F/挂帐)
|
|
2974
|
+
* missing_code_count / missing_code_amount:生成后 _缺失编码 非空的分录(严格维度有名称无编码)
|
|
2908
2975
|
* 通过明细分桶,让 UI 能清楚告诉用户"漏了哪几单、为什么不原因"。 ---- */
|
|
2909
2976
|
const amtFields = ["实收金额", "应收金额", "实付金额", "付款金额", "收款金额", "金额"];
|
|
2977
|
+
const missingCodeBySrc = {};
|
|
2978
|
+
for (const e of out) {
|
|
2979
|
+
if (clean(e["_缺失编码"])) {
|
|
2980
|
+
const src = e["来源单据"] || "";
|
|
2981
|
+
if (!missingCodeBySrc[src]) missingCodeBySrc[src] = { count: 0, amount: 0 };
|
|
2982
|
+
missingCodeBySrc[src].count++;
|
|
2983
|
+
missingCodeBySrc[src].amount += num(e["借方金额"]) + num(e["贷方金额"]);
|
|
2984
|
+
}
|
|
2985
|
+
}
|
|
2910
2986
|
const bySource = [];
|
|
2911
2987
|
for (const curSrc of Object.keys(usedRows)) {
|
|
2912
2988
|
const rows = (opts.biz && opts.biz[curSrc]) || [];
|
|
@@ -2934,6 +3010,7 @@ window.WWT_ENGINE = (function () {
|
|
|
2934
3010
|
金额: amountCol ? num(row[amountCol]) : 0,
|
|
2935
3011
|
});
|
|
2936
3012
|
}
|
|
3013
|
+
const mc = missingCodeBySrc[curSrc] || { count: 0, amount: 0 };
|
|
2937
3014
|
bySource.push({
|
|
2938
3015
|
source: curSrc,
|
|
2939
3016
|
total: rows.length,
|
|
@@ -2944,6 +3021,8 @@ window.WWT_ENGINE = (function () {
|
|
|
2944
3021
|
excluded_amount: round2(excludedAmt),
|
|
2945
3022
|
excluded_clipped: totalUncovered > 200,
|
|
2946
3023
|
excluded_rows: exclRows,
|
|
3024
|
+
missing_code_count: mc.count,
|
|
3025
|
+
missing_code_amount: round2(mc.amount),
|
|
2947
3026
|
unmatched_amount: round2(excludedAmt),
|
|
2948
3027
|
amount_col: amountCol,
|
|
2949
3028
|
never_matched: totalUncovered,
|
|
@@ -2988,6 +3067,31 @@ window.WWT_ENGINE = (function () {
|
|
|
2988
3067
|
};
|
|
2989
3068
|
}
|
|
2990
3069
|
|
|
3070
|
+
/* ---------------- 导出前科目存在性校验 ----------------
|
|
3071
|
+
* 扫描 rows 的「科目编码」列,对照 idx.subjectExists 判断是否在「会计科目表」中存在。
|
|
3072
|
+
* 不存在的科目金蝶导入必失败(报「科目编码在组织 X 中不存在」)。
|
|
3073
|
+
* 返回 [{code, name, count, vnos:[...]}],按出现次数降序。
|
|
3074
|
+
*/
|
|
3075
|
+
function checkSubjects(rows, idx) {
|
|
3076
|
+
const map = {};
|
|
3077
|
+
for (const r of (rows || [])) {
|
|
3078
|
+
const code = clean(r["科目编码"]);
|
|
3079
|
+
if (!code) continue;
|
|
3080
|
+
if (idx && idx.subjectExists && !idx.subjectExists(code)) {
|
|
3081
|
+
if (!map[code]) map[code] = { code: code, name: clean(r["科目名称"] || ""), count: 0, vnos: new Set() };
|
|
3082
|
+
map[code].count++;
|
|
3083
|
+
const vn = clean(r["凭证号"] || "");
|
|
3084
|
+
if (vn) map[code].vnos.add(vn);
|
|
3085
|
+
}
|
|
3086
|
+
}
|
|
3087
|
+
return Object.keys(map).map(k => {
|
|
3088
|
+
const m = map[k];
|
|
3089
|
+
const arr = Array.from(m.vnos);
|
|
3090
|
+
arr.sort((a, b) => (parseFloat(a) || 0) - (parseFloat(b) || 0));
|
|
3091
|
+
return { code: m.code, name: m.name, count: m.count, vnos: arr };
|
|
3092
|
+
}).sort((a, b) => b.count - a.count);
|
|
3093
|
+
}
|
|
3094
|
+
|
|
2991
3095
|
/* ---------------- 单条规则测试预览(voucher preview)----------------
|
|
2992
3096
|
* 与 generate() 共用同一套 evalConditions / calcDimValues / leafSubject 逻辑,
|
|
2993
3097
|
* 只是只跑传入的 rule,并在结果上挂一份"覆盖率审计":
|
|
@@ -3074,14 +3178,26 @@ window.WWT_ENGINE = (function () {
|
|
|
3074
3178
|
for (const it of rowsIn) it._lines = resolveEffectiveLines(r, it.row, curSrc, idx);
|
|
3075
3179
|
|
|
3076
3180
|
const genMode = r.genMode;
|
|
3181
|
+
const gb = r.genGroupBy || "";
|
|
3077
3182
|
let buckets = [];
|
|
3078
3183
|
if (genMode === "一对一") buckets = rowsIn.map(it => [it]);
|
|
3079
|
-
else if (genMode === "全部汇总")
|
|
3080
|
-
|
|
3081
|
-
|
|
3184
|
+
else if (genMode === "全部汇总") {
|
|
3185
|
+
if (gb && gb !== "单位名称") {
|
|
3186
|
+
const m = new Map();
|
|
3187
|
+
for (const it of rowsIn) {
|
|
3188
|
+
const v = clean(it.row[gb]) || "(空)";
|
|
3189
|
+
if (!m.has(v)) m.set(v, []);
|
|
3190
|
+
m.get(v).push(it);
|
|
3191
|
+
}
|
|
3192
|
+
buckets = Array.from(m.values());
|
|
3193
|
+
} else {
|
|
3194
|
+
buckets = [rowsIn];
|
|
3195
|
+
}
|
|
3196
|
+
} else {
|
|
3197
|
+
const useGb = gb || "单位名称";
|
|
3082
3198
|
const m = new Map();
|
|
3083
3199
|
for (const it of rowsIn) {
|
|
3084
|
-
const v = clean(it.row[
|
|
3200
|
+
const v = clean(it.row[useGb]) || "(空)";
|
|
3085
3201
|
if (!m.has(v)) m.set(v, []);
|
|
3086
3202
|
m.get(v).push(it);
|
|
3087
3203
|
}
|
|
@@ -3093,7 +3209,8 @@ window.WWT_ENGINE = (function () {
|
|
|
3093
3209
|
const curVno = vno++;
|
|
3094
3210
|
let eidx = 0;
|
|
3095
3211
|
stats.vouchers++;
|
|
3096
|
-
const groupKeyField = (genMode === "一对一") ? null :
|
|
3212
|
+
const groupKeyField = (genMode === "一对一") ? null :
|
|
3213
|
+
(genMode === "全部汇总" && gb && gb !== "单位名称") ? null : "单位名称";
|
|
3097
3214
|
const subMap = new Map();
|
|
3098
3215
|
for (const it of bucket) {
|
|
3099
3216
|
const gv = groupKeyField ? (clean(it.row[groupKeyField]) || "(空)") : "";
|
|
@@ -3289,6 +3406,8 @@ window.WWT_ENGINE = (function () {
|
|
|
3289
3406
|
// 兼容旧字段(仍叫 never_matched,但其内容仅含真正应处理子集漏命中的行)
|
|
3290
3407
|
never_matched: candidatesMissed.length,
|
|
3291
3408
|
unmatched_amount: round2(unmatchedAmt),
|
|
3409
|
+
missing_code_count: missingCodeCount,
|
|
3410
|
+
missing_code_amount: round2(failedAmount),
|
|
3292
3411
|
amount_col: amountCol,
|
|
3293
3412
|
never_matched_rows: candidatesMissed,
|
|
3294
3413
|
excluded_rows: excludedRows,
|
|
@@ -3462,7 +3581,7 @@ window.WWT_ENGINE = (function () {
|
|
|
3462
3581
|
parseAmtExpr, sumAmtExpr, evalAmtExpr, isAutoDiff, splitFieldsOf,
|
|
3463
3582
|
calcDimValues, leafSubject, resolveEffectiveLines, lookupTypeDim,
|
|
3464
3583
|
isExcludedByRule, isExcludedByGlobals,
|
|
3465
|
-
generate, balanceCheck, buildKingdee, detect, auditRule,
|
|
3584
|
+
generate, balanceCheck, buildKingdee, detect, auditRule, checkSubjects,
|
|
3466
3585
|
lookupMap, lookupTypeMap,
|
|
3467
3586
|
};
|
|
3468
3587
|
})();
|
|
@@ -3486,7 +3605,7 @@ const S = window.__STATE__ = {
|
|
|
3486
3605
|
};
|
|
3487
3606
|
|
|
3488
3607
|
/* ---------------- 版本 & 框架自动更新(只更新程序本身,本地数据/规则不动) ---------------- */
|
|
3489
|
-
const WWT_VER=(function(){const raw='P1.2.
|
|
3608
|
+
const WWT_VER=(function(){const raw='P1.2.49';return raw.indexOf('__WVER__')>=0?'P1.2.10':raw;})();
|
|
3490
3609
|
const UPD_URL_KEY='wwt_fe_updurl';
|
|
3491
3610
|
// 默认更新源:Gitee 上本项目的 version.json(API 接口,浏览器可直接跨域读)
|
|
3492
3611
|
const DEFAULT_UPD_URL='https://gitee.com/api/v5/repos/wwtmrhu/certificate-generation/contents/dist/version.json';
|
|
@@ -3998,7 +4117,7 @@ function _treeRenderBody(){
|
|
|
3998
4117
|
const q=(_treeQ||'').trim().toLowerCase();
|
|
3999
4118
|
const groups=[
|
|
4000
4119
|
{kind:'base', title:'基础数据', slots:C.BASE_SLOTS, total:9},
|
|
4001
|
-
{kind:'biz', title:'业务数据', slots:C.BIZ_SLOTS, total:
|
|
4120
|
+
{kind:'biz', title:'业务数据', slots:C.BIZ_SLOTS, total:9},
|
|
4002
4121
|
];
|
|
4003
4122
|
let html='', vis=0;
|
|
4004
4123
|
for(const g of groups){
|
|
@@ -4774,10 +4893,6 @@ function renderRules(){
|
|
|
4774
4893
|
const conds=(r.conditions||[]).map(c=>'<span class="cond"><b>'+esc(c.field)+'</b> '+esc(c.op)+' '+esc(c.value)+'</span>').join('');
|
|
4775
4894
|
let body='<div class="rulebody" id="rb'+i+'">';
|
|
4776
4895
|
body+='<div style="margin:6px 0"><b>命中条件</b>('+(r.logic||'AND')+'):'+(conds||'<span style="color:#8b949e">无条件(全表命中)</span>')+'</div>';
|
|
4777
|
-
if((r.excludeWhen||[]).length){
|
|
4778
|
-
const exc=r.excludeWhen.map(c=>'<span class="cond"><b>'+esc(c.field)+'</b> '+esc(c.op)+' '+esc(c.value)+'</span>').join('');
|
|
4779
|
-
body+='<div style="background:#fff7e6;padding:6px 8px;border-radius:4px;margin:4px 0"><b style="color:#d46b08">排除条件</b>('+(r.excludeLogic||'AND')+'):'+exc+' <span style="color:#888;font-size:11px">→ 满足的行整条跳过,不进 audit</span></div>';
|
|
4780
|
-
}
|
|
4781
4896
|
if((r.branches||[]).length){
|
|
4782
4897
|
body+='<div><b>分支</b>:'+r.branches.map((b,bi)=>
|
|
4783
4898
|
'<div class="linechip"><span class="tag b">分支'+(bi+1)+' '+esc(b.name||'')+'</span>'+
|
|
@@ -4791,7 +4906,7 @@ function renderRules(){
|
|
|
4791
4906
|
'<span class="tag b" style="margin-left:6px">'+esc(r.id)+'</span>'+
|
|
4792
4907
|
(r.enabled===false?'<span class="tag r" style="margin-left:4px">已停用</span>':'')+'</div>'+
|
|
4793
4908
|
'<div class="rulemeta">来源:'+esc(r.source)+' · 生成:'+esc(r.genMode||'一对一')+
|
|
4794
|
-
(r.genMode==='匹配汇总'?'(按 '+esc(r.genGroupBy||'单位名称')+')':'')+
|
|
4909
|
+
((r.genMode==='匹配汇总' || (r.genMode==='全部汇总' && r.genGroupBy && r.genGroupBy!=='单位名称'))?'(按 '+esc(r.genGroupBy||'单位名称')+')':'')+
|
|
4795
4910
|
(r.lineSplitBy?' · 拆分:'+esc(r.lineSplitBy):'')+'</div></div>'+
|
|
4796
4911
|
'<div style="display:flex;gap:5px"><button class="mini" onclick="UI.toggleRuleBody('+i+')">展开/收起</button>'+
|
|
4797
4912
|
'<button class="mini" onclick="UI.editRule('+i+')">✎ 编辑</button>'+
|
|
@@ -4857,6 +4972,13 @@ function populateEditorDatalists(){
|
|
|
4857
4972
|
'支付方式','结算方式','摘要','备注','凭证号','分录序号','收据号','订单号','单据号','商品名称','型号','规格'];
|
|
4858
4973
|
for(const c of common){ if(!seen.has(c)){ seen.add(c); opts.push('<option value="'+esc(c)+'">'); } }
|
|
4859
4974
|
dlB.innerHTML=opts.join('')||'<option value="(业务数据还没传)">';
|
|
4975
|
+
// 【P1.2.38】金额字段专用下拉 = 业务字段 + 特殊常量
|
|
4976
|
+
const dlA=$('dlAmountFields');
|
|
4977
|
+
if(dlA){
|
|
4978
|
+
const amountOpts=opts.slice();
|
|
4979
|
+
amountOpts.push('<option value="__AUTO_DIFF__">__AUTO_DIFF__(自动补差额)</option>');
|
|
4980
|
+
dlA.innerHTML=amountOpts.join('');
|
|
4981
|
+
}
|
|
4860
4982
|
}
|
|
4861
4983
|
// 0b) 转换:常用 strip_suffix / strip / replace 模板
|
|
4862
4984
|
const dlT=$('dlTransform');
|
|
@@ -5039,7 +5161,7 @@ function ruleFormHTML(){
|
|
|
5039
5161
|
'<span class="w60">业务源</span><select id="rf_src">'+srcOpts+'</select>'+
|
|
5040
5162
|
'<label class="chk"><input type="checkbox" id="rf_en"'+(r.enabled===false?'':' checked')+'>启用</label></div>'+
|
|
5041
5163
|
'<div class="row"><span class="w60">生成方式</span><select id="rf_mode">'+modeOpts+'</select>'+
|
|
5042
|
-
'<span class="w60">分组字段</span><input class="w140" id="rf_gb" value="'+esc(r.genGroupBy||'单位名称')+'" title="
|
|
5164
|
+
'<span class="w60">分组字段</span><input class="w140" id="rf_gb" value="'+esc(r.genGroupBy||'单位名称')+'" title="匹配汇总按此字段拆证;全部汇总如需按此字段拆证也填(默认单位名称表示不拆)">'+
|
|
5043
5165
|
'<span class="w60">拆分字段</span><input class="w180" id="rf_split" value="'+esc(r.lineSplitBy||'')+'" title="多个用 + 或 , 分隔">'+
|
|
5044
5166
|
'<span class="w60">条件逻辑</span><select id="rf_logic">'+logicOpts+'</select></div></div>';
|
|
5045
5167
|
|
|
@@ -5047,18 +5169,6 @@ function ruleFormHTML(){
|
|
|
5047
5169
|
(r.conditions||[]).map((c,i)=>condRowHTML(i,c)).join('')+'</div>'+
|
|
5048
5170
|
'<button class="mini" onclick="UI.addCond()">+ 加条件</button></div>';
|
|
5049
5171
|
|
|
5050
|
-
// 【P1.2.34】规则级排除:满足条件的行整条规则不处理,且不计入覆盖率审计
|
|
5051
|
-
h+='<div class="sec" style="background:#fff7e6;border-left:3px solid #fa8c16"><h4>排除条件(满足这些条件的行<b>整条不处理</b>,不计入审计·不显示在排除/漏命中里)</h4>'+
|
|
5052
|
-
'<div style="font-size:11.5px;color:#888;margin:3px 0">典型场景:「厂牌 = 资产工具库」时由其他流程直接处理,本规则不参与。留空 = 不启用排除。</div>'+
|
|
5053
|
-
'<div class="row"><span class="w60">逻辑</span>'+
|
|
5054
|
-
'<select class="w80" id="rf_exclogic">'+
|
|
5055
|
-
'<option value="AND"'+((r.excludeLogic||'AND')==='AND'?' selected':'')+'>AND(全部满足)</option>'+
|
|
5056
|
-
'<option value="OR"'+(r.excludeLogic==='OR'?' selected':'')+'>OR(任一满足)</option>'+
|
|
5057
|
-
'</select></div>'+
|
|
5058
|
-
'<div id="rf_exconds">'+
|
|
5059
|
-
(r.excludeWhen||[]).map((c,i)=>condRowHTML(i,c)).join('')+'</div>'+
|
|
5060
|
-
'<button class="mini" onclick="UI.addExcludeCond()">+ 加排除条件</button></div>';
|
|
5061
|
-
|
|
5062
5172
|
h+='<div class="sec"><h4>分支(可选:按条件走不同分录;命中第一个分支就停)</h4><div id="rf_branches">'+
|
|
5063
5173
|
(r.branches||[]).map((b,bi)=>branchHTML(bi,b)).join('')+'</div>'+
|
|
5064
5174
|
'<button class="mini" onclick="UI.addBranch()">+ 加分支</button></div>';
|
|
@@ -5098,7 +5208,7 @@ function lineHTML(bi,li,l){
|
|
|
5098
5208
|
return '<div class="sec linerow" data-bi="'+bi+'" data-li="'+li+'" style="background:#fff;margin:5px 0">'+
|
|
5099
5209
|
'<div class="row"><select class="w60" data-lf="side">'+sideOpts+'</select>'+
|
|
5100
5210
|
'<span class="w60">科目</span><input class="w140" data-lf="subject" value="'+esc(l.subject||'')+'" placeholder="如 1122 / 6002" list="dlSubjects" title="可下拉选会计科目表里的编码;也可手动输入">'+
|
|
5101
|
-
'<span class="w60">金额</span><input class="w180" data-lf="amountField" value="'+esc(l.amountField||'')+'" placeholder="实收金额+折扣费 / -折扣费 / 金额*0.13 / 金额/数量" list="
|
|
5211
|
+
'<span class="w60">金额</span><input class="w180" data-lf="amountField" value="'+esc(l.amountField||'')+'" placeholder="实收金额+折扣费 / -折扣费 / 金额*0.13 / 金额/数量" list="dlAmountFields" title="支持四则运算与括号:字段名/数字常量;可下拉选「__AUTO_DIFF__」自动补差额;例:实收金额+折扣费、-折扣费、金额*0.13">'+
|
|
5102
5212
|
'<button class="mini del" onclick="UI.delRow(this,\'linerow\')">删除分录</button></div>'+
|
|
5103
5213
|
'<div class="row"><span class="w60">摘要</span><input class="grow" data-lf="summary" value="'+esc(l.summary||'')+'" placeholder="{单位名称}{收入板块}对帐S{原单号}"></div>'+
|
|
5104
5214
|
'<div style="font-size:11.5px;color:#57606a;margin:3px 0">辅助核算维度:</div>'+
|
|
@@ -5128,14 +5238,6 @@ function syncFormToObj(){
|
|
|
5128
5238
|
return {field:g('field').value.trim(),op:g('op').value,value:g('value').value.trim(),
|
|
5129
5239
|
logic:g('logic').value||undefined};
|
|
5130
5240
|
});
|
|
5131
|
-
// 【P1.2.34】排除条件:满足时整条规则不处理、不进 audit
|
|
5132
|
-
r.excludeLogic=$('rf_exclogic') ? $('rf_exclogic').value : 'AND';
|
|
5133
|
-
const exConds=[...document.querySelectorAll('#rf_exconds > .condrow')].map(row=>{
|
|
5134
|
-
const g=f=>row.querySelector('[data-f="'+f+'"]');
|
|
5135
|
-
return {field:g('field').value.trim(),op:g('op').value,value:g('value').value.trim(),
|
|
5136
|
-
logic:g('logic').value||undefined};
|
|
5137
|
-
});
|
|
5138
|
-
r.excludeWhen=exConds.length ? exConds : undefined;
|
|
5139
5241
|
// 分支
|
|
5140
5242
|
const bsecs=[...document.querySelectorAll('#rf_branches > [data-bi]')];
|
|
5141
5243
|
r.branches=bsecs.map(sec=>{
|
|
@@ -5209,7 +5311,7 @@ function doGenerate(){
|
|
|
5209
5311
|
const bs=(res.audit&&res.audit.by_source)||[];
|
|
5210
5312
|
if(bs.length){
|
|
5211
5313
|
h+='<div class="banner" style="margin-top:6px"><b>📊 业务源覆盖率</b> <span style="color:#57606a;font-size:12px">(按源表汇总:被覆盖行 / 真漏行(候选未命中) / 被排除行(不满足条件);点开看具体单号)</span>'+
|
|
5212
|
-
'<table style="margin-top:6px"><thead><tr><th>源表</th><th class="num">总行</th><th class="num">已覆盖</th><th class="num">占比</th><th class="num">真漏(候选未命中)</th><th class="num">漏算金额</th><th class="num">被排除(不满足条件)</th><th>金额字段</th><th>说明</th></tr></thead><tbody>'+
|
|
5314
|
+
'<table style="margin-top:6px"><thead><tr><th>源表</th><th class="num">总行</th><th class="num">已覆盖</th><th class="num">占比</th><th class="num">真漏(候选未命中)</th><th class="num">漏算金额</th><th class="num">被排除(不满足条件)</th><th class="num">缺编码</th><th>金额字段</th><th>说明</th></tr></thead><tbody>'+
|
|
5213
5315
|
bs.map(b=>{
|
|
5214
5316
|
const cm=b.candidate_missed||0;
|
|
5215
5317
|
const excl=b.excluded||0;
|
|
@@ -5229,6 +5331,8 @@ function doGenerate(){
|
|
|
5229
5331
|
if(firstExcl.原单号) reasonBits.push('原单号='+firstExcl.原单号);
|
|
5230
5332
|
}
|
|
5231
5333
|
const exclReason = reasonBits.slice(0,2).join(',');
|
|
5334
|
+
const mc=b.missing_code_count||0;
|
|
5335
|
+
const ma=b.missing_code_amount||0;
|
|
5232
5336
|
return '<tr'+cmCls+'>'+
|
|
5233
5337
|
'<td><code>'+esc(b.source)+'</code></td>'+
|
|
5234
5338
|
'<td class="num">'+total+'</td>'+
|
|
@@ -5256,10 +5360,13 @@ function doGenerate(){
|
|
|
5256
5360
|
'</details>'+
|
|
5257
5361
|
'</div>'
|
|
5258
5362
|
: '<span style="color:#1a7f37">✅ 0</span>')+'</td>'+
|
|
5363
|
+
'<td class="num"'+(mc?' style="color:#a40e26;font-weight:600"':'')+'>'+
|
|
5364
|
+
(mc>0?'⚠ '+mc+' 行 / ¥'+fmt(ma):'<span style="color:#1a7f37">✅ 0</span>')+'</td>'+
|
|
5259
5365
|
'<td>'+(b.amount_col?esc(b.amount_col):'<span style="color:#8b949e">(无)</span>')+'</td>'+
|
|
5260
5366
|
'<td>'+(cm>0
|
|
5261
5367
|
? '<span style="color:#a40e26">⚠ '+cm+' 行满足条件却未生成凭证(点开被排除行明细确认是规则没命中还是缺编码)</span>'
|
|
5262
5368
|
: '<span style="color:#1a7f37">✅ 真漏 0 行</span>')+
|
|
5369
|
+
(mc>0?'<br><span style="color:#a40e26;font-weight:600;font-size:11.5px">⚠ 该源有 '+mc+' 条分录缺维度编码(金额 ¥'+fmt(ma)+'),去「凭证审核」或「对碰检测」看明细并补基础档案</span>':'')+
|
|
5263
5370
|
(excl>0?'<br><span style="color:#92400e;font-weight:600;font-size:11.5px">另外 '+excl+' 行被本批规则排除(金额 ¥'+fmt(exclAmount)+'),点开「展开全部明细」看哪些单</span>':'')+
|
|
5264
5371
|
'</td></tr>';
|
|
5265
5372
|
}).join('')+
|
|
@@ -5301,6 +5408,31 @@ function doGenerate(){
|
|
|
5301
5408
|
* 底部按钮:📤 导出试入金蝶(直接下载本规则的金蝶导入 Excel)/ 关闭
|
|
5302
5409
|
* ============================================================ */
|
|
5303
5410
|
let _previewCache=null; // 缓存最近一次预览结果,给 exportRulePreview 复用
|
|
5411
|
+
|
|
5412
|
+
/* 【P1.2.48】金蝶式核算维度单列展示:把客户/供应商/部门/员工/仓库/收入类型/成本类型等
|
|
5413
|
+
* 编码+名称合并成「编码/名称」格式,多个维度用 ; 分隔,缺失编码的红色高亮提示。
|
|
5414
|
+
*/
|
|
5415
|
+
function formatDimensions(r){
|
|
5416
|
+
const parts=[]; const missingDims=[];
|
|
5417
|
+
for(const d of C.DIM_LIST){
|
|
5418
|
+
const [nameCol,codeCol]=C.DIM_DISPLAY[d];
|
|
5419
|
+
const name=E.clean(r[nameCol]||''), code=E.clean(r[codeCol]||'');
|
|
5420
|
+
if(!name && !code) continue;
|
|
5421
|
+
let txt='';
|
|
5422
|
+
if(code && name) txt=code+'/'+name;
|
|
5423
|
+
else if(code) txt=code;
|
|
5424
|
+
else txt=name;
|
|
5425
|
+
if(name && !code && C.STRICT_DIMS.indexOf(d)>=0){
|
|
5426
|
+
txt='<span class="dim-missing" title="'+esc(d)+' 缺编码">'+esc(txt)+'⚠</span>';
|
|
5427
|
+
missingDims.push(d);
|
|
5428
|
+
}else{
|
|
5429
|
+
txt=esc(txt);
|
|
5430
|
+
}
|
|
5431
|
+
parts.push(txt);
|
|
5432
|
+
}
|
|
5433
|
+
return {html: parts.join('; '), missingDims: missingDims};
|
|
5434
|
+
}
|
|
5435
|
+
|
|
5304
5436
|
function unmatchedTableHtml(rows, sourceKey){
|
|
5305
5437
|
const partyLabel = esc((C.SOURCE_META[sourceKey] && C.SOURCE_META[sourceKey].party) || '单位名称');
|
|
5306
5438
|
return '<div class="tablewrap" style="max-height:280px;margin-top:6px"><table><thead><tr>'+
|
|
@@ -5364,7 +5496,7 @@ function previewRule(ruleId){
|
|
|
5364
5496
|
|
|
5365
5497
|
let h='<div class="modalbox" style="max-width:980px"><div class="modalhd"><h3>🧪 规则预览 · '+esc(r.name||r.id)+
|
|
5366
5498
|
'</h3><span style="color:#57606a;font-size:12px;margin-left:8px">'+esc(r.source)+' · '+esc(r.genMode||'一对一')+
|
|
5367
|
-
(r.genMode==='匹配汇总'?'(按 '+esc(r.genGroupBy||'单位名称')+')':'')+' · '+linesTxt+'</span>'+
|
|
5499
|
+
((r.genMode==='匹配汇总' || (r.genMode==='全部汇总' && r.genGroupBy && r.genGroupBy!=='单位名称'))?'(按 '+esc(r.genGroupBy||'单位名称')+')':'')+' · '+linesTxt+'</span>'+
|
|
5368
5500
|
'<button class="mini" onclick="UI.closeModal()">✕ 关闭</button></div><div class="modalbd">';
|
|
5369
5501
|
|
|
5370
5502
|
// 顶部 banner:先说"覆盖率 N% + 命中数 / 应处理子集",再分别说"应处理子集漏命中" 与 "源表被规则排除"
|
|
@@ -5426,23 +5558,35 @@ function previewRule(ruleId){
|
|
|
5426
5558
|
if(!pvRows.length){
|
|
5427
5559
|
h+='<div style="color:#8b949e;padding:8px">本规则没生成任何凭证('+esc(r0.candidate_in_source?'无候选行':'源表是空的')+')。</div>';
|
|
5428
5560
|
}else{
|
|
5561
|
+
// 【P1.2.48】凭证预览表格改为金蝶式「核算维度」单列
|
|
5562
|
+
let previewMissingDims=[];
|
|
5429
5563
|
h+='<div class="tablewrap" style="max-height:340px"><table><thead><tr>'+
|
|
5430
5564
|
'<th>凭证号</th><th>分录序号</th><th>业务日期</th><th>摘要</th><th>科目</th><th>借方</th><th>贷方</th>'+
|
|
5431
|
-
'<th
|
|
5432
|
-
pvRows.map(e=>
|
|
5433
|
-
|
|
5434
|
-
|
|
5435
|
-
|
|
5436
|
-
'<
|
|
5437
|
-
|
|
5438
|
-
|
|
5439
|
-
|
|
5440
|
-
|
|
5441
|
-
|
|
5442
|
-
|
|
5443
|
-
|
|
5444
|
-
|
|
5565
|
+
'<th>核算维度</th></tr></thead><tbody>'+
|
|
5566
|
+
pvRows.map(e=>{
|
|
5567
|
+
const dim=formatDimensions(e);
|
|
5568
|
+
if(dim.missingDims.length) previewMissingDims=previewMissingDims.concat(dim.missingDims);
|
|
5569
|
+
const hasMissing=e['_缺失编码'] || dim.missingDims.length>0;
|
|
5570
|
+
return '<tr'+(hasMissing?' class="bad"':'')+'>'+
|
|
5571
|
+
'<td><b>'+esc(e['凭证号'])+'</b></td><td>'+esc(e['分录序号'])+'</td>'+
|
|
5572
|
+
'<td>'+esc(e['业务日期']||'')+'</td><td>'+esc((e['摘要']||'').slice(0,40))+'</td>'+
|
|
5573
|
+
'<td><b>'+esc(e['科目编码'])+'</b> '+esc(e['科目名称']||'')+'</td>'+
|
|
5574
|
+
'<td class="num">'+(e['借方金额']?fmt(e['借方金额']):'')+'</td>'+
|
|
5575
|
+
'<td class="num">'+(e['贷方金额']?fmt(e['贷方金额']):'')+'</td>'+
|
|
5576
|
+
'<td>'+dim.html+'</td></tr>';
|
|
5577
|
+
}).join('')+
|
|
5578
|
+
'</tbody></table></div>';
|
|
5579
|
+
const previewMissingSet=previewMissingDims.filter((v,i,a)=>a.indexOf(v)===i);
|
|
5580
|
+
h+='<div style="margin-top:5px;color:#57606a;font-size:12px">共 <b>'+pvStats.vouchers+'</b> 张凭证,<b>'+
|
|
5581
|
+
pvStats.entries+'</b> 条分录'+
|
|
5582
|
+
(previewMissingSet.length?' · <b style="color:#a40e26">'+previewMissingSet.length+' 个维度缺编码('+
|
|
5583
|
+
previewMissingSet.join('、')+')</b>':' · 无编码缺失')+
|
|
5445
5584
|
')。预览不修改任何数据,关闭即丢。</div>';
|
|
5585
|
+
if(previewMissingSet.length){
|
|
5586
|
+
h+='<div class="banner warn" style="margin-top:6px">⚠ 核算维度缺编码:'+
|
|
5587
|
+
previewMissingSet.map(d=>esc(d)).join('、')+
|
|
5588
|
+
'。请到「⑥ 对碰检测」或「② 映射配置」补全基础档案/映射。</div>';
|
|
5589
|
+
}
|
|
5446
5590
|
}
|
|
5447
5591
|
|
|
5448
5592
|
h+='</div><div class="modalft">'+
|
|
@@ -5474,12 +5618,18 @@ function exportRulePreview(){
|
|
|
5474
5618
|
const ma=[C.MISSING_HEAD].concat(pMissing.map(m=>C.MISSING_HEAD.map(h=>m[h]||'')));
|
|
5475
5619
|
XLSX.utils.book_append_sheet(wb, XLSX.utils.aoa_to_sheet(ma),'缺失编码清单');
|
|
5476
5620
|
}
|
|
5621
|
+
const pSubj = E.checkSubjects(pRows, idx);
|
|
5622
|
+
if(pSubj.length){
|
|
5623
|
+
const ma=[C.SUBJ_MISS_HEAD].concat(pSubj.map(m=>[m.code,m.name,m.count,m.vnos.join(', ')]));
|
|
5624
|
+
XLSX.utils.book_append_sheet(wb, XLSX.utils.aoa_to_sheet(ma),'缺失科目清单');
|
|
5625
|
+
}
|
|
5477
5626
|
const fname='金蝶导入_'+ (r.name||r.id).replace(/[\\/:*?"<>|]/g,'_').slice(0,30) + '_' + r.id + '.xlsx';
|
|
5478
5627
|
XLSX.writeFile(wb, fname);
|
|
5479
5628
|
const whFbk = aoa.whFallback || 0;
|
|
5480
5629
|
const incFbk = aoa.incMiss || 0;
|
|
5481
5630
|
let msg='已导出 '+fname+(whFbk?'(含 '+whFbk+' 条仓库 LJ 兜底)':'');
|
|
5482
5631
|
if (incFbk) msg += '(含 '+incFbk+' 条 6050 收入类型兜底,建议去「③ 规则」补 dim 收入类型)';
|
|
5632
|
+
if (pSubj.length) msg += '(⚠ '+pSubj.length+' 个科目编码在会计科目表里查不到,金蝶导入会失败)';
|
|
5483
5633
|
toast(msg);
|
|
5484
5634
|
}catch(e){ toast('导出失败:'+e.message); }
|
|
5485
5635
|
}
|
|
@@ -5487,8 +5637,9 @@ function exportRulePreview(){
|
|
|
5487
5637
|
/* ============================================================
|
|
5488
5638
|
* ⑤ 凭证审核
|
|
5489
5639
|
* ============================================================ */
|
|
5640
|
+
// 【P1.2.48】审核表格把客户/供应商/部门/员工/仓库/收入类型/成本类型等合并成「核算维度」单列
|
|
5490
5641
|
const AUD_COLS=['凭证号','分录序号','记账日期','业务日期','摘要','科目编码','科目名称','借方金额','贷方金额',
|
|
5491
|
-
'
|
|
5642
|
+
'核算维度','结算方式名称','来源单据','规则名称'];
|
|
5492
5643
|
const AUD_NUM=['借方金额','贷方金额'];
|
|
5493
5644
|
let audPage=1;
|
|
5494
5645
|
/** S.draft 里"分组来源":cv_upload | 业务源 key(如 销售已确认) */
|
|
@@ -5561,7 +5712,12 @@ function renderAudit(){
|
|
|
5561
5712
|
/* ---- 3) 选组过滤 ---- */
|
|
5562
5713
|
let rows=S.draft.map((r,i)=>({r:r,i:i,g:_srcGroup(r)}));
|
|
5563
5714
|
if(group!=='all') rows=rows.filter(o=>o.g===group);
|
|
5564
|
-
if(kw) rows=rows.filter(o=>
|
|
5715
|
+
if(kw) rows=rows.filter(o=>
|
|
5716
|
+
AUD_COLS.some(c=>{
|
|
5717
|
+
if(c==='核算维度') return formatDimensions(o.r).html.replace(/<[^>]+>/g,'').toLowerCase().indexOf(kw)>=0;
|
|
5718
|
+
return String(nz(o.r[c])).toLowerCase().indexOf(kw)>=0;
|
|
5719
|
+
})
|
|
5720
|
+
);
|
|
5565
5721
|
|
|
5566
5722
|
/* ---- 4) 不平标记(只算当前 visible 组)---- */
|
|
5567
5723
|
const bad={};
|
|
@@ -5577,8 +5733,13 @@ function renderAudit(){
|
|
|
5577
5733
|
let h='<table><thead><tr><th style="width:34px"></th>'+AUD_COLS.map(c=>'<th>'+esc(c)+'</th>').join('')+'</tr></thead><tbody>';
|
|
5578
5734
|
h+=slice.map(o=>{
|
|
5579
5735
|
const r=o.r;
|
|
5580
|
-
|
|
5736
|
+
const dim=formatDimensions(r);
|
|
5737
|
+
const hasMissing=dim.missingDims.length>0;
|
|
5738
|
+
return '<tr'+(bad[o.i]||hasMissing?' class="bad"':'')+'><td><button class="mini del" onclick="UI.delDraftRow('+o.i+')">删</button></td>'+
|
|
5581
5739
|
AUD_COLS.map(c=>{
|
|
5740
|
+
if(c==='核算维度'){
|
|
5741
|
+
return '<td>'+dim.html+'</td>';
|
|
5742
|
+
}
|
|
5582
5743
|
const v=nz(r[c]);
|
|
5583
5744
|
const canEdit=['摘要','科目编码','借方金额','贷方金额','记账日期','业务日期'].indexOf(c)>=0||C.DIM_LIST.some(d=>C.DIM_DISPLAY[d][0]===c||C.DIM_DISPLAY[d][1]===c);
|
|
5584
5745
|
const cls=AUD_NUM.indexOf(c)>=0?'num':'';
|
|
@@ -5714,17 +5875,29 @@ function doExport(){
|
|
|
5714
5875
|
const ma=[C.MISSING_HEAD].concat(S.missing.map(m=>C.MISSING_HEAD.map(h=>m[h]||'')));
|
|
5715
5876
|
XLSX.utils.book_append_sheet(wb,XLSX.utils.aoa_to_sheet(ma),'缺失编码清单');
|
|
5716
5877
|
}
|
|
5878
|
+
const missSubj = E.checkSubjects(S.draft, idx);
|
|
5879
|
+
if (missSubj.length) {
|
|
5880
|
+
const ma = [C.SUBJ_MISS_HEAD].concat(missSubj.map(m => [m.code, m.name, m.count, m.vnos.join(", ")]));
|
|
5881
|
+
XLSX.utils.book_append_sheet(wb, XLSX.utils.aoa_to_sheet(ma), '缺失科目清单');
|
|
5882
|
+
}
|
|
5717
5883
|
const fname='金蝶导入.xlsx';
|
|
5718
5884
|
XLSX.writeFile(wb,fname);
|
|
5719
5885
|
// 【P1.2.12】把仓库空兜底条数展示给用户,让他知道哪些行被自动补了
|
|
5720
5886
|
const whFbk = aoa.whFallback || 0;
|
|
5721
5887
|
// 【P1.2.21】6050 收入类型兜底条数(应该 =0;>0 提示用户该规则行漏配 dim 收入类型)
|
|
5722
5888
|
const incFbk = aoa.incMiss || 0;
|
|
5889
|
+
const subjWarn = missSubj.length ?
|
|
5890
|
+
'<div class="banner warn" style="margin-top:8px"><b>⚠ 有 '+missSubj.length+' 个科目编码在「会计科目表」里查不到,金蝶导入必失败!</b>'+
|
|
5891
|
+
'<table style="margin-top:7px"><thead><tr><th>科目编码</th><th>科目名称</th><th>出现次数</th><th>涉及凭证号</th></tr></thead><tbody>'+
|
|
5892
|
+
missSubj.map(m=>'<tr><td><b>'+esc(m.code)+'</b></td><td>'+esc(m.name||'(空)')+'</td><td class="num">'+m.count+'</td><td>'+esc(m.vnos.join(', ')||'-')+'</td></tr>').join('')+
|
|
5893
|
+
'</tbody></table><div style="margin-top:6px">请到「③ 规则」把借方/贷方科目改成「会计科目表」里存在的末级编码;或在金蝶 WWT 组织里新增这些科目后,重新上传「会计科目表」再导出。</div></div>'
|
|
5894
|
+
: '';
|
|
5723
5895
|
$('expBanner').innerHTML='<div class="banner ok">✅ 已导出 <b>'+fname+'</b>:'+S.draft.length+' 条分录,'+
|
|
5724
5896
|
(aoa.length-2)+' 行数据'+(S.missing&&S.missing.length?',另含「缺失编码清单」'+S.missing.length+' 行':'')+
|
|
5897
|
+
(missSubj.length?',另含「缺失科目清单」'+missSubj.length+' 行':'')+
|
|
5725
5898
|
(whFbk ? ',<b>🔧 '+whFbk+' 行仓库列被自动补 LJ 兜底</b>(建议到「② 映射」补厂牌→仓库映射后重跑)' : '')+
|
|
5726
5899
|
(incFbk ? ',<b>🛟 '+incFbk+' 行 6050 收入类型被自动补 089</b>(你这条规则漏了「收入类型」维度,建议去「③ 规则」补上)' : '')+
|
|
5727
|
-
'</div>';
|
|
5900
|
+
'</div>'+subjWarn;
|
|
5728
5901
|
$('expStat').innerHTML='文件已下载到浏览器的下载目录(一般为「下载」文件夹)。导入金蝶前请确认:<b>前两行(字段键行)不要删</b>。'+
|
|
5729
5902
|
(whFbk ? '<br><span style="color:#d4880f">⚠ '+whFbk+' 行仓库列被自动补 LJ:去「② 映射→① 厂牌→仓库」点「🔄 自动扫描」把所有业务厂牌拉进来,下次导出就不会再兜底了。</span>' : '')+
|
|
5730
5903
|
(incFbk ? '<br><span style="color:#d4880f">⚠ '+incFbk+' 行 6050(其他业务收入)缺「收入类型」维度,引擎已自动补 089/其他业务收入-其他。建议去「③ 规则」找对应规则的 6050 行,加上 dim=收入类型 fixedCode=089 fixedValue=其他业务收入-其他。</span>' : '');
|
|
@@ -6887,13 +7060,6 @@ window.UI={
|
|
|
6887
7060
|
else editing.conditions.push({field:'',op:'=',value:'',logic:''});
|
|
6888
7061
|
redrawRuleForm();
|
|
6889
7062
|
},
|
|
6890
|
-
// 【P1.2.34】加排除条件:写到 #rf_exconds
|
|
6891
|
-
addExcludeCond:()=>{
|
|
6892
|
-
syncFormToObj();
|
|
6893
|
-
if(!editing.excludeWhen) editing.excludeWhen=[];
|
|
6894
|
-
editing.excludeWhen.push({field:'',op:'=',value:'',logic:''});
|
|
6895
|
-
redrawRuleForm();
|
|
6896
|
-
},
|
|
6897
7063
|
addBranch:()=>{
|
|
6898
7064
|
syncFormToObj();
|
|
6899
7065
|
editing.branches.push({name:'分支'+((editing.branches||[]).length+1),logic:'AND',conditions:[{field:'',op:'=',value:''}],lines:[]});
|
package/dist/version.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
|
-
"ver": "P1.2.
|
|
3
|
-
"npm": "1.2.
|
|
4
|
-
"url": "https://cdn.jsdelivr.net/npm/wwt-certificate-tool@1.2.
|
|
5
|
-
"url_unpkg": "https://unpkg.com/wwt-certificate-tool@1.2.
|
|
6
|
-
"url_gh": "https://api.github.com/repos/huweipei745839/certificate-generation/contents/dist/WWT%E5%87%AD%E8%AF%81%E5%B7%A5%E5%85%B7_P1.2.
|
|
7
|
-
"note": "
|
|
2
|
+
"ver": "P1.2.49",
|
|
3
|
+
"npm": "1.2.49",
|
|
4
|
+
"url": "https://cdn.jsdelivr.net/npm/wwt-certificate-tool@1.2.49/dist/app.html",
|
|
5
|
+
"url_unpkg": "https://unpkg.com/wwt-certificate-tool@1.2.49/dist/app.html",
|
|
6
|
+
"url_gh": "https://api.github.com/repos/huweipei745839/certificate-generation/contents/dist/WWT%E5%87%AD%E8%AF%81%E5%B7%A5%E5%85%B7_P1.2.49.html",
|
|
7
|
+
"note": "① 规则测试预览与凭证审核界面改为金蝶式「核算维度」单列展示(客户/供应商/部门/员工/仓库/收入类型/成本类型合并为一列,编码/名称格式)。② 核算维度缺失编码时单元格内标红并带 ⚠ 提示。③ 对碰检测覆盖「入库未结算」中付款方式为挂账的数据,即使供应商尚未建档也报缺失。④ 1403 开头存货科目若规则未配仓库维度,默认带出厂牌 LJ/零件(LJ仓)。⑤ 业务源覆盖率表格新增「缺编码」列,按源表显示缺编码分录行数与金额。"
|
|
8
8
|
}
|