wwt-certificate-tool 1.2.35 → 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 +284 -83
- 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}
|
|
@@ -206,6 +207,14 @@ tr.bad:hover td{background:#fbe9e9}
|
|
|
206
207
|
.sec input,.sec select{padding:4px 7px;border:1px solid #d0d7de;border-radius:5px;font-size:12px;font-family:inherit}
|
|
207
208
|
.sec input:focus,.sec select:focus{outline:none;border-color:#2f6feb}
|
|
208
209
|
.w30{width:30px}.w60{width:60px}.w80{width:80px}.w110{width:110px}.w140{width:140px}.w180{width:180px}.w240{width:240px}.grow{flex:1;min-width:130px}
|
|
210
|
+
/* 【P1.2.36】全局排除的字段输入框:下拉箭头 + 提示色,让「点选」一目了然 */
|
|
211
|
+
input[data-f="field"][list^="dlGe_"]{
|
|
212
|
+
background:#fffbea url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 16 16'><path fill='%23866a1f' d='M3 6l5 5 5-5z'/></svg>") no-repeat right 6px center;
|
|
213
|
+
background-size:10px 10px;cursor:pointer;
|
|
214
|
+
border-color:#e0c97a;
|
|
215
|
+
}
|
|
216
|
+
input[data-f="field"][list^="dlGe_"]:focus{background-color:#fff7d4;border-color:#e0a526}
|
|
217
|
+
input[data-f="field"][list^="dlGe_"]::placeholder{color:#a8861f;font-style:italic}
|
|
209
218
|
|
|
210
219
|
/* ============================================================
|
|
211
220
|
* 📋 通用表格编辑器(grid)
|
|
@@ -307,7 +316,7 @@ tr.bad:hover td{background:#fbe9e9}
|
|
|
307
316
|
<div class="brand">
|
|
308
317
|
<span class="logo">WWT</span>
|
|
309
318
|
<div>
|
|
310
|
-
<h1>金蝶凭证工具 <span class="ver">P1.2.
|
|
319
|
+
<h1>金蝶凭证工具 <span class="ver">P1.2.49</span></h1>
|
|
311
320
|
<div class="sub">纯前端离线版 · 双击即用 · 数据只存在本机,不上传任何服务器</div>
|
|
312
321
|
</div>
|
|
313
322
|
</div>
|
|
@@ -340,7 +349,7 @@ tr.bad:hover td{background:#fbe9e9}
|
|
|
340
349
|
<!-- ① 上传 -->
|
|
341
350
|
<section class="pane on" id="tab-upload">
|
|
342
351
|
<div class="notice">
|
|
343
|
-
<b>第一次用请按顺序来:</b>先传 <b>9 个基础数据表</b>(一次传完,以后不用再传)→ 再传 <b>本月
|
|
352
|
+
<b>第一次用请按顺序来:</b>先传 <b>9 个基础数据表</b>(一次传完,以后不用再传)→ 再传 <b>本月 9 个业务数据表</b> → 去「② 映射配置」改月份 → 「④ 生成凭证」→「⑦ 导出金蝶」。
|
|
344
353
|
<br><b>业务数据表必须是 .xls 原文件,不要改名、不要另存为 .xlsx。</b>
|
|
345
354
|
</div>
|
|
346
355
|
<div class="rowacts">
|
|
@@ -349,7 +358,7 @@ tr.bad:hover td{background:#fbe9e9}
|
|
|
349
358
|
</div>
|
|
350
359
|
<h3>基础数据(9 个 · 一次上传长期有效)<span class="tip" style="margin-left:8px">支持 <b>点击按钮</b> 或 <b>把 Excel 文件拖到这里</b></span></h3>
|
|
351
360
|
<div class="grid dropzone" id="grpBase" data-kind="base"></div>
|
|
352
|
-
<h3>本月业务数据(
|
|
361
|
+
<h3>本月业务数据(9 个 · 每月重传)<span class="tip" style="margin-left:8px">支持 <b>点击按钮</b> 或 <b>把 .xls 文件拖到这里</b></span></h3>
|
|
353
362
|
<div class="grid dropzone" id="grpBiz" data-kind="biz"></div>
|
|
354
363
|
<div id="uploadLog" class="log"></div>
|
|
355
364
|
<input type="file" id="pickBase" multiple accept=".xls,.xlsx" style="display:none">
|
|
@@ -514,6 +523,8 @@ tr.bad:hover td{background:#fbe9e9}
|
|
|
514
523
|
<datalist id="dlSubjects"></datalist>
|
|
515
524
|
<datalist id="dlAuxFixed"></datalist>
|
|
516
525
|
<datalist id="dlBizFields"></datalist>
|
|
526
|
+
<!-- 【P1.2.38】金额字段专用下拉:在业务字段基础上加上 __AUTO_DIFF__ 自动补差额等常量 -->
|
|
527
|
+
<datalist id="dlAmountFields"></datalist>
|
|
517
528
|
<datalist id="dlTransform"></datalist>
|
|
518
529
|
<datalist id="dlAuxValue"></datalist>
|
|
519
530
|
|
|
@@ -814,6 +825,7 @@ window.WWT_CONST = (function () {
|
|
|
814
825
|
"FOPPACCTNAME","FITEMNUM","FITEMNAME","FSUBITEMNUM","FSUBITEMNAME","FCURRENCYNUM","FCURRENCYNAME",
|
|
815
826
|
"FAMOUNTFOR","FAMOUNT"];
|
|
816
827
|
const MISSING_HEAD = ["来源单据","业务编号","科目","维度","名称"];
|
|
828
|
+
const SUBJ_MISS_HEAD = ["科目编码","科目名称","出现次数","涉及凭证号"];
|
|
817
829
|
|
|
818
830
|
/* ---------- 16 种辅助核算维度 ---------- */
|
|
819
831
|
// 草稿列名:[名称列, 编码列]
|
|
@@ -862,6 +874,8 @@ window.WWT_CONST = (function () {
|
|
|
862
874
|
"调拨未确认":{"file":"调拨未确认.xls","sheet":"调拨总","datecol":"出库日期","party":"单位名称","partytype":"客户","no":"出库单号"},
|
|
863
875
|
"在修车":{"file":"在修车.xls","sheet":"在修车","datecol":"进厂日期","party":"单位名称","partytype":"客户","no":"工单号"},
|
|
864
876
|
"已完工未结算":{"file":"已完工未结算.xls","sheet":"完工未结","datecol":"结算日期","party":"单位名称","partytype":"客户","no":"工单号"},
|
|
877
|
+
// 【P1.2.39】新增第 9 个业务源:维修已结算(按实际表头 维修已结算.xls 校准)
|
|
878
|
+
"维修已结算":{"file":"维修已结算.xls","sheet":"维修已结算","datecol":"结算日期","party":"单位名称","partytype":"客户","no":"工单号"},
|
|
865
879
|
};
|
|
866
880
|
const BIZ_SLOTS = [
|
|
867
881
|
{key:"入库已确认", label:"入库已确认(采购-已确认)"},
|
|
@@ -872,9 +886,13 @@ window.WWT_CONST = (function () {
|
|
|
872
886
|
{key:"调拨未确认", label:"调拨未确认"},
|
|
873
887
|
{key:"在修车", label:"在修车(维修在修)"},
|
|
874
888
|
{key:"已完工未结算", label:"已完工未结算(维修完工)"},
|
|
889
|
+
{key:"维修已结算", label:"维修已结算(维修已结)"},
|
|
875
890
|
];
|
|
876
891
|
// 对碰检测只覆盖"已确认/已结算"的来源;已完工未结算 用户要求不参与(2026-09-09)
|
|
877
|
-
|
|
892
|
+
// 【P1.2.39】新增 维修已结算(已结算类,按设计意图纳入对碰检测)
|
|
893
|
+
// 【P1.2.48】对碰检测增加「入库未结算」:用户要求付款方式=挂账的入库数据,
|
|
894
|
+
// 即使供应商尚未在基础供应商表建档,也要报出来,便于补档案/补映射。
|
|
895
|
+
const DETECT_ALLOWED_SOURCES = ["入库已确认","入库未结算","销售已确认","调拨已确认","维修已结算"];
|
|
878
896
|
// v1.2.20:只核对 挂账 / 对账S / 冲转S 三类(去掉 对帐F / 冲转F)
|
|
879
897
|
const DETECT_ALLOWED_PAY = ["对帐S","挂帐","冲转S"];
|
|
880
898
|
|
|
@@ -966,7 +984,7 @@ window.WWT_CONST = (function () {
|
|
|
966
984
|
const JOIN_PREFIX = ["客户","供应商","部门","员工","其他往来"];
|
|
967
985
|
|
|
968
986
|
return {
|
|
969
|
-
KINGDEE_COLUMNS, KINGDEE_NAMES, CASHFLOW_HEAD, MISSING_HEAD,
|
|
987
|
+
KINGDEE_COLUMNS, KINGDEE_NAMES, CASHFLOW_HEAD, MISSING_HEAD, SUBJ_MISS_HEAD,
|
|
970
988
|
DIM_DISPLAY, DIM_COLUMNS, DIM_LIST, STRICT_DIMS,
|
|
971
989
|
SOURCE_META, BIZ_SLOTS, BASE_SLOTS,
|
|
972
990
|
DETECT_ALLOWED_SOURCES, DETECT_ALLOWED_PAY,
|
|
@@ -2473,11 +2491,19 @@ window.WWT_ENGINE = (function () {
|
|
|
2473
2491
|
if (m && clean(m.whcode)) return { code: clean(m.whcode), name: clean(m.whname) || clean(m.whcode) };
|
|
2474
2492
|
return { code: "LJ", name: "LJ仓" };
|
|
2475
2493
|
}
|
|
2476
|
-
//
|
|
2494
|
+
// 维度反查:优先当前维度对应的基础表,再兜底其他表
|
|
2495
|
+
// 【P1.2.45】修复同名单位跨表串码:供应商维度先查供应商表,客户维度先查客户表。
|
|
2477
2496
|
function lookupDimCode(dim, name, idx) {
|
|
2478
2497
|
if (!name) return "";
|
|
2479
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) 兜底:保持原六表顺序(兼容历史模糊匹配场景)
|
|
2480
2505
|
for (const d of order) {
|
|
2506
|
+
if (d === dim) continue; // 已查过
|
|
2481
2507
|
const t = idx.tables[d];
|
|
2482
2508
|
if (!t) continue;
|
|
2483
2509
|
const rec = t[name] || ciGetObj(t, name);
|
|
@@ -2517,15 +2543,27 @@ window.WWT_ENGINE = (function () {
|
|
|
2517
2543
|
code = lookupDimCode(d, name, idx) || lookupDimByField(d, vf, name, idx);
|
|
2518
2544
|
}
|
|
2519
2545
|
} else {
|
|
2520
|
-
|
|
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);
|
|
2521
2555
|
if (/\.编码$/.test(vf) && name) { code = name; }
|
|
2522
2556
|
if (name) {
|
|
2523
2557
|
if (d === "仓库") {
|
|
2524
2558
|
const w = lookupWh(name);
|
|
2525
2559
|
code = w.code; name = w.name;
|
|
2526
2560
|
} else if (d === "收入类型" || d === "成本类型") {
|
|
2527
|
-
// 硬约束:只认「出库单类 /
|
|
2528
|
-
|
|
2561
|
+
// 硬约束:只认「出库单类 / 单类」,盘亏清空,其它来源字段一律清空;
|
|
2562
|
+
// 【P1.2.41】维修已结算没有「单类」,允许用「收费类型」映射成本类型;
|
|
2563
|
+
// 【P1.2.42】同一例外扩展到 收入类型(收费类型 → 收入类型 也可走映射表)。
|
|
2564
|
+
const allowVf = (vf === "出库单类" || vf === "单类") ||
|
|
2565
|
+
(curSrc === "维修已结算" && vf === "收费类型");
|
|
2566
|
+
if (allowVf) {
|
|
2529
2567
|
if (name.indexOf("盘亏") >= 0) { name = ""; code = ""; }
|
|
2530
2568
|
else {
|
|
2531
2569
|
const r = lookupTypeDim(d, name, vf, idx);
|
|
@@ -2538,7 +2576,17 @@ window.WWT_ENGINE = (function () {
|
|
|
2538
2576
|
code = lookupDimCode(d, name, idx) || lookupDimByField(d, vf, name, idx);
|
|
2539
2577
|
if (!code) {
|
|
2540
2578
|
const t = idx.tables[d];
|
|
2541
|
-
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
|
+
}
|
|
2542
2590
|
}
|
|
2543
2591
|
}
|
|
2544
2592
|
}
|
|
@@ -2555,6 +2603,16 @@ window.WWT_ENGINE = (function () {
|
|
|
2555
2603
|
}
|
|
2556
2604
|
out[d] = { name: name, code: code };
|
|
2557
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
|
+
|
|
2558
2616
|
return out;
|
|
2559
2617
|
}
|
|
2560
2618
|
|
|
@@ -2603,7 +2661,7 @@ window.WWT_ENGINE = (function () {
|
|
|
2603
2661
|
function truncateBiz(s, sep) {
|
|
2604
2662
|
const arr = clean(s).split(sep).filter(Boolean);
|
|
2605
2663
|
if (arr.length <= 3) return arr.join(sep);
|
|
2606
|
-
return arr.slice(0, 3).join(sep) + sep + "
|
|
2664
|
+
return arr.slice(0, 3).join(sep) + sep + "等共" + arr.length + "张";
|
|
2607
2665
|
}
|
|
2608
2666
|
function buildSummaryCtx(items, rule, curSrc) {
|
|
2609
2667
|
const meta = C.SOURCE_META[curSrc] || {};
|
|
@@ -2752,16 +2810,28 @@ window.WWT_ENGINE = (function () {
|
|
|
2752
2810
|
|
|
2753
2811
|
// Step A:分桶(决定几张凭证)
|
|
2754
2812
|
const genMode = opts.modeOverride || rule.genMode || "一对一";
|
|
2813
|
+
const gb = opts.groupByOverride || rule.genGroupBy || "";
|
|
2755
2814
|
let buckets = [];
|
|
2756
2815
|
if (genMode === "一对一") {
|
|
2757
2816
|
buckets = rowsIn.map(it => [it]);
|
|
2758
2817
|
} else if (genMode === "全部汇总") {
|
|
2759
|
-
|
|
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
|
+
}
|
|
2760
2830
|
} else {
|
|
2761
|
-
const
|
|
2831
|
+
const useGb = gb || "单位名称";
|
|
2762
2832
|
const m = new Map();
|
|
2763
2833
|
for (const it of rowsIn) {
|
|
2764
|
-
const v = clean(it.row[
|
|
2834
|
+
const v = clean(it.row[useGb]) || "(空)";
|
|
2765
2835
|
if (!m.has(v)) m.set(v, []);
|
|
2766
2836
|
m.get(v).push(it);
|
|
2767
2837
|
}
|
|
@@ -2777,11 +2847,15 @@ window.WWT_ENGINE = (function () {
|
|
|
2777
2847
|
stats.vouchers++;
|
|
2778
2848
|
|
|
2779
2849
|
// Step B:sub_bucket = group_key值 × 分支lines
|
|
2780
|
-
|
|
2850
|
+
// 【P1.2.40】全部汇总且按非单位名称字段拆证时,取消子桶按单位名称细分,实现真正的按该字段汇总
|
|
2851
|
+
// 【P1.2.46】若规则配置了「拆分字段」,子桶按拆分字段拆分,不再按单位名称细分(实现 1 证内按供应商等字段分录)
|
|
2852
|
+
const groupKeyField = (genMode === "一对一" || splitFs.length > 0) ? null :
|
|
2853
|
+
(genMode === "全部汇总" && gb && gb !== "单位名称") ? null : "单位名称";
|
|
2781
2854
|
const subMap = new Map();
|
|
2782
2855
|
for (const it of bucket) {
|
|
2783
2856
|
const gv = groupKeyField ? (clean(it.row[groupKeyField]) || "(空)") : "";
|
|
2784
|
-
const
|
|
2857
|
+
const splitT = splitFs.map(f => clean(it.row[f]));
|
|
2858
|
+
const k = JSON.stringify([gv, splitT, linesKey(it._lines)]);
|
|
2785
2859
|
if (!subMap.has(k)) subMap.set(k, { gv: gv, lines: it._lines, items: [], acct: {} });
|
|
2786
2860
|
subMap.get(k).items.push(it);
|
|
2787
2861
|
}
|
|
@@ -2897,8 +2971,18 @@ window.WWT_ENGINE = (function () {
|
|
|
2897
2971
|
* covered:被规则命中(candidate→产出凭证)
|
|
2898
2972
|
* candidate_missed:满足某条规则全部 = 条件、但未生成凭证(一般 = 缺编码/客户未匹配);P1.2.15 起 generate() 主循环无此中间态,固定 0
|
|
2899
2973
|
* excluded:既不满足任一规则的 = 条件,也未被命中(设计排除——例:本场景对账S规则不接 冲转F/挂帐)
|
|
2974
|
+
* missing_code_count / missing_code_amount:生成后 _缺失编码 非空的分录(严格维度有名称无编码)
|
|
2900
2975
|
* 通过明细分桶,让 UI 能清楚告诉用户"漏了哪几单、为什么不原因"。 ---- */
|
|
2901
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
|
+
}
|
|
2902
2986
|
const bySource = [];
|
|
2903
2987
|
for (const curSrc of Object.keys(usedRows)) {
|
|
2904
2988
|
const rows = (opts.biz && opts.biz[curSrc]) || [];
|
|
@@ -2926,6 +3010,7 @@ window.WWT_ENGINE = (function () {
|
|
|
2926
3010
|
金额: amountCol ? num(row[amountCol]) : 0,
|
|
2927
3011
|
});
|
|
2928
3012
|
}
|
|
3013
|
+
const mc = missingCodeBySrc[curSrc] || { count: 0, amount: 0 };
|
|
2929
3014
|
bySource.push({
|
|
2930
3015
|
source: curSrc,
|
|
2931
3016
|
total: rows.length,
|
|
@@ -2936,6 +3021,8 @@ window.WWT_ENGINE = (function () {
|
|
|
2936
3021
|
excluded_amount: round2(excludedAmt),
|
|
2937
3022
|
excluded_clipped: totalUncovered > 200,
|
|
2938
3023
|
excluded_rows: exclRows,
|
|
3024
|
+
missing_code_count: mc.count,
|
|
3025
|
+
missing_code_amount: round2(mc.amount),
|
|
2939
3026
|
unmatched_amount: round2(excludedAmt),
|
|
2940
3027
|
amount_col: amountCol,
|
|
2941
3028
|
never_matched: totalUncovered,
|
|
@@ -2980,6 +3067,31 @@ window.WWT_ENGINE = (function () {
|
|
|
2980
3067
|
};
|
|
2981
3068
|
}
|
|
2982
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
|
+
|
|
2983
3095
|
/* ---------------- 单条规则测试预览(voucher preview)----------------
|
|
2984
3096
|
* 与 generate() 共用同一套 evalConditions / calcDimValues / leafSubject 逻辑,
|
|
2985
3097
|
* 只是只跑传入的 rule,并在结果上挂一份"覆盖率审计":
|
|
@@ -3066,14 +3178,26 @@ window.WWT_ENGINE = (function () {
|
|
|
3066
3178
|
for (const it of rowsIn) it._lines = resolveEffectiveLines(r, it.row, curSrc, idx);
|
|
3067
3179
|
|
|
3068
3180
|
const genMode = r.genMode;
|
|
3181
|
+
const gb = r.genGroupBy || "";
|
|
3069
3182
|
let buckets = [];
|
|
3070
3183
|
if (genMode === "一对一") buckets = rowsIn.map(it => [it]);
|
|
3071
|
-
else if (genMode === "全部汇总")
|
|
3072
|
-
|
|
3073
|
-
|
|
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 || "单位名称";
|
|
3074
3198
|
const m = new Map();
|
|
3075
3199
|
for (const it of rowsIn) {
|
|
3076
|
-
const v = clean(it.row[
|
|
3200
|
+
const v = clean(it.row[useGb]) || "(空)";
|
|
3077
3201
|
if (!m.has(v)) m.set(v, []);
|
|
3078
3202
|
m.get(v).push(it);
|
|
3079
3203
|
}
|
|
@@ -3085,7 +3209,8 @@ window.WWT_ENGINE = (function () {
|
|
|
3085
3209
|
const curVno = vno++;
|
|
3086
3210
|
let eidx = 0;
|
|
3087
3211
|
stats.vouchers++;
|
|
3088
|
-
const groupKeyField = (genMode === "一对一") ? null :
|
|
3212
|
+
const groupKeyField = (genMode === "一对一") ? null :
|
|
3213
|
+
(genMode === "全部汇总" && gb && gb !== "单位名称") ? null : "单位名称";
|
|
3089
3214
|
const subMap = new Map();
|
|
3090
3215
|
for (const it of bucket) {
|
|
3091
3216
|
const gv = groupKeyField ? (clean(it.row[groupKeyField]) || "(空)") : "";
|
|
@@ -3281,6 +3406,8 @@ window.WWT_ENGINE = (function () {
|
|
|
3281
3406
|
// 兼容旧字段(仍叫 never_matched,但其内容仅含真正应处理子集漏命中的行)
|
|
3282
3407
|
never_matched: candidatesMissed.length,
|
|
3283
3408
|
unmatched_amount: round2(unmatchedAmt),
|
|
3409
|
+
missing_code_count: missingCodeCount,
|
|
3410
|
+
missing_code_amount: round2(failedAmount),
|
|
3284
3411
|
amount_col: amountCol,
|
|
3285
3412
|
never_matched_rows: candidatesMissed,
|
|
3286
3413
|
excluded_rows: excludedRows,
|
|
@@ -3454,7 +3581,7 @@ window.WWT_ENGINE = (function () {
|
|
|
3454
3581
|
parseAmtExpr, sumAmtExpr, evalAmtExpr, isAutoDiff, splitFieldsOf,
|
|
3455
3582
|
calcDimValues, leafSubject, resolveEffectiveLines, lookupTypeDim,
|
|
3456
3583
|
isExcludedByRule, isExcludedByGlobals,
|
|
3457
|
-
generate, balanceCheck, buildKingdee, detect, auditRule,
|
|
3584
|
+
generate, balanceCheck, buildKingdee, detect, auditRule, checkSubjects,
|
|
3458
3585
|
lookupMap, lookupTypeMap,
|
|
3459
3586
|
};
|
|
3460
3587
|
})();
|
|
@@ -3478,7 +3605,7 @@ const S = window.__STATE__ = {
|
|
|
3478
3605
|
};
|
|
3479
3606
|
|
|
3480
3607
|
/* ---------------- 版本 & 框架自动更新(只更新程序本身,本地数据/规则不动) ---------------- */
|
|
3481
|
-
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;})();
|
|
3482
3609
|
const UPD_URL_KEY='wwt_fe_updurl';
|
|
3483
3610
|
// 默认更新源:Gitee 上本项目的 version.json(API 接口,浏览器可直接跨域读)
|
|
3484
3611
|
const DEFAULT_UPD_URL='https://gitee.com/api/v5/repos/wwtmrhu/certificate-generation/contents/dist/version.json';
|
|
@@ -3990,7 +4117,7 @@ function _treeRenderBody(){
|
|
|
3990
4117
|
const q=(_treeQ||'').trim().toLowerCase();
|
|
3991
4118
|
const groups=[
|
|
3992
4119
|
{kind:'base', title:'基础数据', slots:C.BASE_SLOTS, total:9},
|
|
3993
|
-
{kind:'biz', title:'业务数据', slots:C.BIZ_SLOTS, total:
|
|
4120
|
+
{kind:'biz', title:'业务数据', slots:C.BIZ_SLOTS, total:9},
|
|
3994
4121
|
];
|
|
3995
4122
|
let html='', vis=0;
|
|
3996
4123
|
for(const g of groups){
|
|
@@ -4766,10 +4893,6 @@ function renderRules(){
|
|
|
4766
4893
|
const conds=(r.conditions||[]).map(c=>'<span class="cond"><b>'+esc(c.field)+'</b> '+esc(c.op)+' '+esc(c.value)+'</span>').join('');
|
|
4767
4894
|
let body='<div class="rulebody" id="rb'+i+'">';
|
|
4768
4895
|
body+='<div style="margin:6px 0"><b>命中条件</b>('+(r.logic||'AND')+'):'+(conds||'<span style="color:#8b949e">无条件(全表命中)</span>')+'</div>';
|
|
4769
|
-
if((r.excludeWhen||[]).length){
|
|
4770
|
-
const exc=r.excludeWhen.map(c=>'<span class="cond"><b>'+esc(c.field)+'</b> '+esc(c.op)+' '+esc(c.value)+'</span>').join('');
|
|
4771
|
-
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>';
|
|
4772
|
-
}
|
|
4773
4896
|
if((r.branches||[]).length){
|
|
4774
4897
|
body+='<div><b>分支</b>:'+r.branches.map((b,bi)=>
|
|
4775
4898
|
'<div class="linechip"><span class="tag b">分支'+(bi+1)+' '+esc(b.name||'')+'</span>'+
|
|
@@ -4783,7 +4906,7 @@ function renderRules(){
|
|
|
4783
4906
|
'<span class="tag b" style="margin-left:6px">'+esc(r.id)+'</span>'+
|
|
4784
4907
|
(r.enabled===false?'<span class="tag r" style="margin-left:4px">已停用</span>':'')+'</div>'+
|
|
4785
4908
|
'<div class="rulemeta">来源:'+esc(r.source)+' · 生成:'+esc(r.genMode||'一对一')+
|
|
4786
|
-
(r.genMode==='匹配汇总'?'(按 '+esc(r.genGroupBy||'单位名称')+')':'')+
|
|
4909
|
+
((r.genMode==='匹配汇总' || (r.genMode==='全部汇总' && r.genGroupBy && r.genGroupBy!=='单位名称'))?'(按 '+esc(r.genGroupBy||'单位名称')+')':'')+
|
|
4787
4910
|
(r.lineSplitBy?' · 拆分:'+esc(r.lineSplitBy):'')+'</div></div>'+
|
|
4788
4911
|
'<div style="display:flex;gap:5px"><button class="mini" onclick="UI.toggleRuleBody('+i+')">展开/收起</button>'+
|
|
4789
4912
|
'<button class="mini" onclick="UI.editRule('+i+')">✎ 编辑</button>'+
|
|
@@ -4849,6 +4972,13 @@ function populateEditorDatalists(){
|
|
|
4849
4972
|
'支付方式','结算方式','摘要','备注','凭证号','分录序号','收据号','订单号','单据号','商品名称','型号','规格'];
|
|
4850
4973
|
for(const c of common){ if(!seen.has(c)){ seen.add(c); opts.push('<option value="'+esc(c)+'">'); } }
|
|
4851
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
|
+
}
|
|
4852
4982
|
}
|
|
4853
4983
|
// 0b) 转换:常用 strip_suffix / strip / replace 模板
|
|
4854
4984
|
const dlT=$('dlTransform');
|
|
@@ -5031,7 +5161,7 @@ function ruleFormHTML(){
|
|
|
5031
5161
|
'<span class="w60">业务源</span><select id="rf_src">'+srcOpts+'</select>'+
|
|
5032
5162
|
'<label class="chk"><input type="checkbox" id="rf_en"'+(r.enabled===false?'':' checked')+'>启用</label></div>'+
|
|
5033
5163
|
'<div class="row"><span class="w60">生成方式</span><select id="rf_mode">'+modeOpts+'</select>'+
|
|
5034
|
-
'<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="匹配汇总按此字段拆证;全部汇总如需按此字段拆证也填(默认单位名称表示不拆)">'+
|
|
5035
5165
|
'<span class="w60">拆分字段</span><input class="w180" id="rf_split" value="'+esc(r.lineSplitBy||'')+'" title="多个用 + 或 , 分隔">'+
|
|
5036
5166
|
'<span class="w60">条件逻辑</span><select id="rf_logic">'+logicOpts+'</select></div></div>';
|
|
5037
5167
|
|
|
@@ -5039,18 +5169,6 @@ function ruleFormHTML(){
|
|
|
5039
5169
|
(r.conditions||[]).map((c,i)=>condRowHTML(i,c)).join('')+'</div>'+
|
|
5040
5170
|
'<button class="mini" onclick="UI.addCond()">+ 加条件</button></div>';
|
|
5041
5171
|
|
|
5042
|
-
// 【P1.2.34】规则级排除:满足条件的行整条规则不处理,且不计入覆盖率审计
|
|
5043
|
-
h+='<div class="sec" style="background:#fff7e6;border-left:3px solid #fa8c16"><h4>排除条件(满足这些条件的行<b>整条不处理</b>,不计入审计·不显示在排除/漏命中里)</h4>'+
|
|
5044
|
-
'<div style="font-size:11.5px;color:#888;margin:3px 0">典型场景:「厂牌 = 资产工具库」时由其他流程直接处理,本规则不参与。留空 = 不启用排除。</div>'+
|
|
5045
|
-
'<div class="row"><span class="w60">逻辑</span>'+
|
|
5046
|
-
'<select class="w80" id="rf_exclogic">'+
|
|
5047
|
-
'<option value="AND"'+((r.excludeLogic||'AND')==='AND'?' selected':'')+'>AND(全部满足)</option>'+
|
|
5048
|
-
'<option value="OR"'+(r.excludeLogic==='OR'?' selected':'')+'>OR(任一满足)</option>'+
|
|
5049
|
-
'</select></div>'+
|
|
5050
|
-
'<div id="rf_exconds">'+
|
|
5051
|
-
(r.excludeWhen||[]).map((c,i)=>condRowHTML(i,c)).join('')+'</div>'+
|
|
5052
|
-
'<button class="mini" onclick="UI.addExcludeCond()">+ 加排除条件</button></div>';
|
|
5053
|
-
|
|
5054
5172
|
h+='<div class="sec"><h4>分支(可选:按条件走不同分录;命中第一个分支就停)</h4><div id="rf_branches">'+
|
|
5055
5173
|
(r.branches||[]).map((b,bi)=>branchHTML(bi,b)).join('')+'</div>'+
|
|
5056
5174
|
'<button class="mini" onclick="UI.addBranch()">+ 加分支</button></div>';
|
|
@@ -5060,10 +5178,12 @@ function ruleFormHTML(){
|
|
|
5060
5178
|
'<button class="mini" onclick="UI.addLine(-1)">+ 加分录</button></div>';
|
|
5061
5179
|
return h;
|
|
5062
5180
|
}
|
|
5063
|
-
function condRowHTML(i,c){
|
|
5181
|
+
function condRowHTML(i,c,srcKey){
|
|
5064
5182
|
const opOpts=C.OPS.map(o=>'<option value="'+esc(o)+'"'+(c.op===o?' selected':'')+'>'+esc(o)+'</option>').join('');
|
|
5183
|
+
// 【P1.2.36】全局排除分组按业务源独立 datalist(避免看到别的业务源的字段)
|
|
5184
|
+
const listId = srcKey ? 'dlGe_' + srcKey : 'dlBizFields';
|
|
5065
5185
|
return '<div class="row condrow" data-i="'+i+'">'+
|
|
5066
|
-
'<input class="w140" data-f="field" value="'+esc(c.field||'')+'" placeholder="
|
|
5186
|
+
'<input class="w140" data-f="field" value="'+esc(c.field||'')+'" placeholder="点此选字段" list="'+listId+'">'+
|
|
5067
5187
|
'<select class="w80" data-f="op">'+opOpts+'</select>'+
|
|
5068
5188
|
'<input class="w180" data-f="value" value="'+esc(c.value===undefined?'':c.value)+'" placeholder="比较值">'+
|
|
5069
5189
|
'<select class="w80" data-f="logic"><option value=""'+(!c.logic?' selected':'')+'>并且</option>'+
|
|
@@ -5088,7 +5208,7 @@ function lineHTML(bi,li,l){
|
|
|
5088
5208
|
return '<div class="sec linerow" data-bi="'+bi+'" data-li="'+li+'" style="background:#fff;margin:5px 0">'+
|
|
5089
5209
|
'<div class="row"><select class="w60" data-lf="side">'+sideOpts+'</select>'+
|
|
5090
5210
|
'<span class="w60">科目</span><input class="w140" data-lf="subject" value="'+esc(l.subject||'')+'" placeholder="如 1122 / 6002" list="dlSubjects" title="可下拉选会计科目表里的编码;也可手动输入">'+
|
|
5091
|
-
'<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">'+
|
|
5092
5212
|
'<button class="mini del" onclick="UI.delRow(this,\'linerow\')">删除分录</button></div>'+
|
|
5093
5213
|
'<div class="row"><span class="w60">摘要</span><input class="grow" data-lf="summary" value="'+esc(l.summary||'')+'" placeholder="{单位名称}{收入板块}对帐S{原单号}"></div>'+
|
|
5094
5214
|
'<div style="font-size:11.5px;color:#57606a;margin:3px 0">辅助核算维度:</div>'+
|
|
@@ -5118,14 +5238,6 @@ function syncFormToObj(){
|
|
|
5118
5238
|
return {field:g('field').value.trim(),op:g('op').value,value:g('value').value.trim(),
|
|
5119
5239
|
logic:g('logic').value||undefined};
|
|
5120
5240
|
});
|
|
5121
|
-
// 【P1.2.34】排除条件:满足时整条规则不处理、不进 audit
|
|
5122
|
-
r.excludeLogic=$('rf_exclogic') ? $('rf_exclogic').value : 'AND';
|
|
5123
|
-
const exConds=[...document.querySelectorAll('#rf_exconds > .condrow')].map(row=>{
|
|
5124
|
-
const g=f=>row.querySelector('[data-f="'+f+'"]');
|
|
5125
|
-
return {field:g('field').value.trim(),op:g('op').value,value:g('value').value.trim(),
|
|
5126
|
-
logic:g('logic').value||undefined};
|
|
5127
|
-
});
|
|
5128
|
-
r.excludeWhen=exConds.length ? exConds : undefined;
|
|
5129
5241
|
// 分支
|
|
5130
5242
|
const bsecs=[...document.querySelectorAll('#rf_branches > [data-bi]')];
|
|
5131
5243
|
r.branches=bsecs.map(sec=>{
|
|
@@ -5199,7 +5311,7 @@ function doGenerate(){
|
|
|
5199
5311
|
const bs=(res.audit&&res.audit.by_source)||[];
|
|
5200
5312
|
if(bs.length){
|
|
5201
5313
|
h+='<div class="banner" style="margin-top:6px"><b>📊 业务源覆盖率</b> <span style="color:#57606a;font-size:12px">(按源表汇总:被覆盖行 / 真漏行(候选未命中) / 被排除行(不满足条件);点开看具体单号)</span>'+
|
|
5202
|
-
'<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>'+
|
|
5203
5315
|
bs.map(b=>{
|
|
5204
5316
|
const cm=b.candidate_missed||0;
|
|
5205
5317
|
const excl=b.excluded||0;
|
|
@@ -5219,6 +5331,8 @@ function doGenerate(){
|
|
|
5219
5331
|
if(firstExcl.原单号) reasonBits.push('原单号='+firstExcl.原单号);
|
|
5220
5332
|
}
|
|
5221
5333
|
const exclReason = reasonBits.slice(0,2).join(',');
|
|
5334
|
+
const mc=b.missing_code_count||0;
|
|
5335
|
+
const ma=b.missing_code_amount||0;
|
|
5222
5336
|
return '<tr'+cmCls+'>'+
|
|
5223
5337
|
'<td><code>'+esc(b.source)+'</code></td>'+
|
|
5224
5338
|
'<td class="num">'+total+'</td>'+
|
|
@@ -5246,10 +5360,13 @@ function doGenerate(){
|
|
|
5246
5360
|
'</details>'+
|
|
5247
5361
|
'</div>'
|
|
5248
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>'+
|
|
5249
5365
|
'<td>'+(b.amount_col?esc(b.amount_col):'<span style="color:#8b949e">(无)</span>')+'</td>'+
|
|
5250
5366
|
'<td>'+(cm>0
|
|
5251
5367
|
? '<span style="color:#a40e26">⚠ '+cm+' 行满足条件却未生成凭证(点开被排除行明细确认是规则没命中还是缺编码)</span>'
|
|
5252
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>':'')+
|
|
5253
5370
|
(excl>0?'<br><span style="color:#92400e;font-weight:600;font-size:11.5px">另外 '+excl+' 行被本批规则排除(金额 ¥'+fmt(exclAmount)+'),点开「展开全部明细」看哪些单</span>':'')+
|
|
5254
5371
|
'</td></tr>';
|
|
5255
5372
|
}).join('')+
|
|
@@ -5291,6 +5408,31 @@ function doGenerate(){
|
|
|
5291
5408
|
* 底部按钮:📤 导出试入金蝶(直接下载本规则的金蝶导入 Excel)/ 关闭
|
|
5292
5409
|
* ============================================================ */
|
|
5293
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
|
+
|
|
5294
5436
|
function unmatchedTableHtml(rows, sourceKey){
|
|
5295
5437
|
const partyLabel = esc((C.SOURCE_META[sourceKey] && C.SOURCE_META[sourceKey].party) || '单位名称');
|
|
5296
5438
|
return '<div class="tablewrap" style="max-height:280px;margin-top:6px"><table><thead><tr>'+
|
|
@@ -5354,7 +5496,7 @@ function previewRule(ruleId){
|
|
|
5354
5496
|
|
|
5355
5497
|
let h='<div class="modalbox" style="max-width:980px"><div class="modalhd"><h3>🧪 规则预览 · '+esc(r.name||r.id)+
|
|
5356
5498
|
'</h3><span style="color:#57606a;font-size:12px;margin-left:8px">'+esc(r.source)+' · '+esc(r.genMode||'一对一')+
|
|
5357
|
-
(r.genMode==='匹配汇总'?'(按 '+esc(r.genGroupBy||'单位名称')+')':'')+' · '+linesTxt+'</span>'+
|
|
5499
|
+
((r.genMode==='匹配汇总' || (r.genMode==='全部汇总' && r.genGroupBy && r.genGroupBy!=='单位名称'))?'(按 '+esc(r.genGroupBy||'单位名称')+')':'')+' · '+linesTxt+'</span>'+
|
|
5358
5500
|
'<button class="mini" onclick="UI.closeModal()">✕ 关闭</button></div><div class="modalbd">';
|
|
5359
5501
|
|
|
5360
5502
|
// 顶部 banner:先说"覆盖率 N% + 命中数 / 应处理子集",再分别说"应处理子集漏命中" 与 "源表被规则排除"
|
|
@@ -5416,23 +5558,35 @@ function previewRule(ruleId){
|
|
|
5416
5558
|
if(!pvRows.length){
|
|
5417
5559
|
h+='<div style="color:#8b949e;padding:8px">本规则没生成任何凭证('+esc(r0.candidate_in_source?'无候选行':'源表是空的')+')。</div>';
|
|
5418
5560
|
}else{
|
|
5561
|
+
// 【P1.2.48】凭证预览表格改为金蝶式「核算维度」单列
|
|
5562
|
+
let previewMissingDims=[];
|
|
5419
5563
|
h+='<div class="tablewrap" style="max-height:340px"><table><thead><tr>'+
|
|
5420
5564
|
'<th>凭证号</th><th>分录序号</th><th>业务日期</th><th>摘要</th><th>科目</th><th>借方</th><th>贷方</th>'+
|
|
5421
|
-
'<th
|
|
5422
|
-
pvRows.map(e=>
|
|
5423
|
-
|
|
5424
|
-
|
|
5425
|
-
|
|
5426
|
-
'<
|
|
5427
|
-
|
|
5428
|
-
|
|
5429
|
-
|
|
5430
|
-
|
|
5431
|
-
|
|
5432
|
-
|
|
5433
|
-
|
|
5434
|
-
|
|
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>':' · 无编码缺失')+
|
|
5435
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
|
+
}
|
|
5436
5590
|
}
|
|
5437
5591
|
|
|
5438
5592
|
h+='</div><div class="modalft">'+
|
|
@@ -5464,12 +5618,18 @@ function exportRulePreview(){
|
|
|
5464
5618
|
const ma=[C.MISSING_HEAD].concat(pMissing.map(m=>C.MISSING_HEAD.map(h=>m[h]||'')));
|
|
5465
5619
|
XLSX.utils.book_append_sheet(wb, XLSX.utils.aoa_to_sheet(ma),'缺失编码清单');
|
|
5466
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
|
+
}
|
|
5467
5626
|
const fname='金蝶导入_'+ (r.name||r.id).replace(/[\\/:*?"<>|]/g,'_').slice(0,30) + '_' + r.id + '.xlsx';
|
|
5468
5627
|
XLSX.writeFile(wb, fname);
|
|
5469
5628
|
const whFbk = aoa.whFallback || 0;
|
|
5470
5629
|
const incFbk = aoa.incMiss || 0;
|
|
5471
5630
|
let msg='已导出 '+fname+(whFbk?'(含 '+whFbk+' 条仓库 LJ 兜底)':'');
|
|
5472
5631
|
if (incFbk) msg += '(含 '+incFbk+' 条 6050 收入类型兜底,建议去「③ 规则」补 dim 收入类型)';
|
|
5632
|
+
if (pSubj.length) msg += '(⚠ '+pSubj.length+' 个科目编码在会计科目表里查不到,金蝶导入会失败)';
|
|
5473
5633
|
toast(msg);
|
|
5474
5634
|
}catch(e){ toast('导出失败:'+e.message); }
|
|
5475
5635
|
}
|
|
@@ -5477,8 +5637,9 @@ function exportRulePreview(){
|
|
|
5477
5637
|
/* ============================================================
|
|
5478
5638
|
* ⑤ 凭证审核
|
|
5479
5639
|
* ============================================================ */
|
|
5640
|
+
// 【P1.2.48】审核表格把客户/供应商/部门/员工/仓库/收入类型/成本类型等合并成「核算维度」单列
|
|
5480
5641
|
const AUD_COLS=['凭证号','分录序号','记账日期','业务日期','摘要','科目编码','科目名称','借方金额','贷方金额',
|
|
5481
|
-
'
|
|
5642
|
+
'核算维度','结算方式名称','来源单据','规则名称'];
|
|
5482
5643
|
const AUD_NUM=['借方金额','贷方金额'];
|
|
5483
5644
|
let audPage=1;
|
|
5484
5645
|
/** S.draft 里"分组来源":cv_upload | 业务源 key(如 销售已确认) */
|
|
@@ -5551,7 +5712,12 @@ function renderAudit(){
|
|
|
5551
5712
|
/* ---- 3) 选组过滤 ---- */
|
|
5552
5713
|
let rows=S.draft.map((r,i)=>({r:r,i:i,g:_srcGroup(r)}));
|
|
5553
5714
|
if(group!=='all') rows=rows.filter(o=>o.g===group);
|
|
5554
|
-
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
|
+
);
|
|
5555
5721
|
|
|
5556
5722
|
/* ---- 4) 不平标记(只算当前 visible 组)---- */
|
|
5557
5723
|
const bad={};
|
|
@@ -5567,8 +5733,13 @@ function renderAudit(){
|
|
|
5567
5733
|
let h='<table><thead><tr><th style="width:34px"></th>'+AUD_COLS.map(c=>'<th>'+esc(c)+'</th>').join('')+'</tr></thead><tbody>';
|
|
5568
5734
|
h+=slice.map(o=>{
|
|
5569
5735
|
const r=o.r;
|
|
5570
|
-
|
|
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>'+
|
|
5571
5739
|
AUD_COLS.map(c=>{
|
|
5740
|
+
if(c==='核算维度'){
|
|
5741
|
+
return '<td>'+dim.html+'</td>';
|
|
5742
|
+
}
|
|
5572
5743
|
const v=nz(r[c]);
|
|
5573
5744
|
const canEdit=['摘要','科目编码','借方金额','贷方金额','记账日期','业务日期'].indexOf(c)>=0||C.DIM_LIST.some(d=>C.DIM_DISPLAY[d][0]===c||C.DIM_DISPLAY[d][1]===c);
|
|
5574
5745
|
const cls=AUD_NUM.indexOf(c)>=0?'num':'';
|
|
@@ -5704,17 +5875,29 @@ function doExport(){
|
|
|
5704
5875
|
const ma=[C.MISSING_HEAD].concat(S.missing.map(m=>C.MISSING_HEAD.map(h=>m[h]||'')));
|
|
5705
5876
|
XLSX.utils.book_append_sheet(wb,XLSX.utils.aoa_to_sheet(ma),'缺失编码清单');
|
|
5706
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
|
+
}
|
|
5707
5883
|
const fname='金蝶导入.xlsx';
|
|
5708
5884
|
XLSX.writeFile(wb,fname);
|
|
5709
5885
|
// 【P1.2.12】把仓库空兜底条数展示给用户,让他知道哪些行被自动补了
|
|
5710
5886
|
const whFbk = aoa.whFallback || 0;
|
|
5711
5887
|
// 【P1.2.21】6050 收入类型兜底条数(应该 =0;>0 提示用户该规则行漏配 dim 收入类型)
|
|
5712
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
|
+
: '';
|
|
5713
5895
|
$('expBanner').innerHTML='<div class="banner ok">✅ 已导出 <b>'+fname+'</b>:'+S.draft.length+' 条分录,'+
|
|
5714
5896
|
(aoa.length-2)+' 行数据'+(S.missing&&S.missing.length?',另含「缺失编码清单」'+S.missing.length+' 行':'')+
|
|
5897
|
+
(missSubj.length?',另含「缺失科目清单」'+missSubj.length+' 行':'')+
|
|
5715
5898
|
(whFbk ? ',<b>🔧 '+whFbk+' 行仓库列被自动补 LJ 兜底</b>(建议到「② 映射」补厂牌→仓库映射后重跑)' : '')+
|
|
5716
5899
|
(incFbk ? ',<b>🛟 '+incFbk+' 行 6050 收入类型被自动补 089</b>(你这条规则漏了「收入类型」维度,建议去「③ 规则」补上)' : '')+
|
|
5717
|
-
'</div>';
|
|
5900
|
+
'</div>'+subjWarn;
|
|
5718
5901
|
$('expStat').innerHTML='文件已下载到浏览器的下载目录(一般为「下载」文件夹)。导入金蝶前请确认:<b>前两行(字段键行)不要删</b>。'+
|
|
5719
5902
|
(whFbk ? '<br><span style="color:#d4880f">⚠ '+whFbk+' 行仓库列被自动补 LJ:去「② 映射→① 厂牌→仓库」点「🔄 自动扫描」把所有业务厂牌拉进来,下次导出就不会再兜底了。</span>' : '')+
|
|
5720
5903
|
(incFbk ? '<br><span style="color:#d4880f">⚠ '+incFbk+' 行 6050(其他业务收入)缺「收入类型」维度,引擎已自动补 089/其他业务收入-其他。建议去「③ 规则」找对应规则的 6050 行,加上 dim=收入类型 fixedCode=089 fixedValue=其他业务收入-其他。</span>' : '');
|
|
@@ -6771,14 +6954,35 @@ window.UI={
|
|
|
6771
6954
|
'<br><span style="color:#a40e26">⚠ 全局排除是「粗过滤」,会作用于该业务源下<b>所有规则</b>;想只排除某一条规则请在「编辑规则 → 排除条件」单独配。</span>'+
|
|
6772
6955
|
'</div>';
|
|
6773
6956
|
// 每个业务源一个折叠 details
|
|
6957
|
+
// 【P1.2.36】先构建「每业务源独立 datalist」:只列该业务源上传后的列名(避免看到别的业务源字段)
|
|
6958
|
+
const dlBy = {}; // srcKey -> ['列1','列2',...]
|
|
6959
|
+
for (const s of slots) {
|
|
6960
|
+
const rows = (S.biz && S.biz[s.key]) || [];
|
|
6961
|
+
const seen = new Set();
|
|
6962
|
+
const fields = [];
|
|
6963
|
+
if (rows.length) {
|
|
6964
|
+
const r0 = rows.find(r => r && typeof r === 'object') || {};
|
|
6965
|
+
for (const col of Object.keys(r0)) {
|
|
6966
|
+
const c = String(col || '').trim();
|
|
6967
|
+
if (!c || seen.has(c)) continue;
|
|
6968
|
+
seen.add(c); fields.push(c);
|
|
6969
|
+
}
|
|
6970
|
+
}
|
|
6971
|
+
// 兜底常用字段(S.biz 还没上传时也能给点选项)
|
|
6972
|
+
const common = ['单位名称','客户名称','供应商名称','付款单位','收款单位','部门','仓库','日期','业务日期','记账日期',
|
|
6973
|
+
'金额','实收金额','应收金额','实付金额','付款金额','收款金额','数量','单价','折扣','折扣率',
|
|
6974
|
+
'支付方式','结算方式','摘要','备注','凭证号','分录序号','收据号','订单号','单据号','商品名称','型号','规格'];
|
|
6975
|
+
for (const c of common) { if (!seen.has(c)) { seen.add(c); fields.push(c); } }
|
|
6976
|
+
dlBy[s.key] = fields;
|
|
6977
|
+
}
|
|
6774
6978
|
for (const s of slots) {
|
|
6775
6979
|
const cfg = ge[s.key] || (ge[s.key] = { logic: 'AND', conditions: [] });
|
|
6776
|
-
const condHTML = (cfg.conditions || []).map((c, i) => condRowHTML(i, c)).join('');
|
|
6980
|
+
const condHTML = (cfg.conditions || []).map((c, i) => condRowHTML(i, c, s.key)).join('');
|
|
6777
6981
|
const condsId = 'ge_conds_' + s.key;
|
|
6778
6982
|
html += '<details open style="border:1px solid #dde2e8;border-radius:6px;margin:6px 0;background:#fff">'+
|
|
6779
6983
|
'<summary style="padding:8px 12px;cursor:pointer;font-weight:600">'+
|
|
6780
6984
|
'<span style="display:inline-block;width:160px">'+esc(s.label)+'</span>'+
|
|
6781
|
-
'<span style="color:#a40e26;font-size:12px">['+((cfg.conditions||[]).length)+' 个条件]</span>'+
|
|
6985
|
+
'<span style="color:#a40e26;font-size:12px">['+((cfg.conditions||[]).length)+' 个条件 · '+dlBy[s.key].length+' 个可选字段]</span>'+
|
|
6782
6986
|
'</summary>'+
|
|
6783
6987
|
'<div style="padding:4px 12px 12px">'+
|
|
6784
6988
|
'<div class="row" style="margin-bottom:6px">'+
|
|
@@ -6793,6 +6997,10 @@ window.UI={
|
|
|
6793
6997
|
'<button class="mini" onclick="UI.addGlobalExcludeCond(\''+s.key+'\')">+ 加条件</button>'+
|
|
6794
6998
|
'</div></details>';
|
|
6795
6999
|
}
|
|
7000
|
+
// 把每个业务源独立的 datalist 挂到弹窗里(用完即丢,不污染 dlBizFields)
|
|
7001
|
+
html += '<div style="display:none">'+
|
|
7002
|
+
slots.map(s => '<datalist id="dlGe_'+esc(s.key)+'">'+dlBy[s.key].map(f=>'<option value="'+esc(f)+'">').join('')+'</datalist>').join('')+
|
|
7003
|
+
'</div>';
|
|
6796
7004
|
html += '</div>'+
|
|
6797
7005
|
'<div class="modalft">'+
|
|
6798
7006
|
'<button class="btn ghost" onclick="UI.closeModal()">取消</button>'+
|
|
@@ -6852,13 +7060,6 @@ window.UI={
|
|
|
6852
7060
|
else editing.conditions.push({field:'',op:'=',value:'',logic:''});
|
|
6853
7061
|
redrawRuleForm();
|
|
6854
7062
|
},
|
|
6855
|
-
// 【P1.2.34】加排除条件:写到 #rf_exconds
|
|
6856
|
-
addExcludeCond:()=>{
|
|
6857
|
-
syncFormToObj();
|
|
6858
|
-
if(!editing.excludeWhen) editing.excludeWhen=[];
|
|
6859
|
-
editing.excludeWhen.push({field:'',op:'=',value:'',logic:''});
|
|
6860
|
-
redrawRuleForm();
|
|
6861
|
-
},
|
|
6862
7063
|
addBranch:()=>{
|
|
6863
7064
|
syncFormToObj();
|
|
6864
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
|
}
|