wwt-certificate-tool 1.2.33 → 1.2.35
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 +186 -7
- 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.35</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}
|
|
@@ -52,6 +52,8 @@ main{padding:14px 18px 60px}
|
|
|
52
52
|
.btn.danger{color:#c1121f}
|
|
53
53
|
.btn.danger:hover{background:#fdf0f0;border-color:#e5a3a3}
|
|
54
54
|
.btn.ghost{background:#fafbfc}
|
|
55
|
+
.btn.warn{background:#fa8c16;color:#fff;border-color:#fa8c16}
|
|
56
|
+
.btn.warn:hover{background:#d97a14;border-color:#d97a14}
|
|
55
57
|
.btn.big{padding:9px 22px;font-size:14px}
|
|
56
58
|
.rowacts{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin:10px 0}
|
|
57
59
|
.tip{font-size:11.5px;color:#8b949e}
|
|
@@ -305,7 +307,7 @@ tr.bad:hover td{background:#fbe9e9}
|
|
|
305
307
|
<div class="brand">
|
|
306
308
|
<span class="logo">WWT</span>
|
|
307
309
|
<div>
|
|
308
|
-
<h1>金蝶凭证工具 <span class="ver">P1.2.
|
|
310
|
+
<h1>金蝶凭证工具 <span class="ver">P1.2.35</span></h1>
|
|
309
311
|
<div class="sub">纯前端离线版 · 双击即用 · 数据只存在本机,不上传任何服务器</div>
|
|
310
312
|
</div>
|
|
311
313
|
</div>
|
|
@@ -372,9 +374,10 @@ tr.bad:hover td{background:#fbe9e9}
|
|
|
372
374
|
<div class="rowacts">
|
|
373
375
|
<button class="btn" onclick="UI.newRule()">✚ 新增规则</button>
|
|
374
376
|
<button class="btn" onclick="UI.saveCfg()">💾 保存所有规则</button>
|
|
377
|
+
<button class="btn warn" onclick="UI.openGlobalExcludes()">🚫 全局排除(总开关)</button>
|
|
375
378
|
<button class="btn ghost" onclick="UI.exportRules()">📤 导出规则</button>
|
|
376
379
|
<button class="btn ghost" onclick="UI.importRules()">📥 导入规则</button>
|
|
377
|
-
<span class="tip"
|
|
380
|
+
<span class="tip">规则改动要点「保存所有规则」才会生效;全局排除在弹窗里保存后即生效(也写入本地存档)</span>
|
|
378
381
|
</div>
|
|
379
382
|
<div id="ruleTabs" class="src-tabs"></div>
|
|
380
383
|
<div id="ruleList"></div>
|
|
@@ -2643,6 +2646,26 @@ window.WWT_ENGINE = (function () {
|
|
|
2643
2646
|
}
|
|
2644
2647
|
return (rule.lines || []).slice();
|
|
2645
2648
|
}
|
|
2649
|
+
|
|
2650
|
+
// 【P1.2.34】规则级排除:当一行满足 rule.excludeWhen 全部条件时,
|
|
2651
|
+
// 本规则不处理,且不计入「被本规则排除」/「应处理子集漏命中」任何审计统计——
|
|
2652
|
+
// 业务上等同于这条行从未出现在本规则的视野里(用于「厂牌=资产工具库」这类
|
|
2653
|
+
// 由其他专门流程处理的合法跳过场景,避免覆盖率审计误报警报)。
|
|
2654
|
+
function isExcludedByRule(row, rule, curSrc, idx) {
|
|
2655
|
+
const ex = rule && rule.excludeWhen;
|
|
2656
|
+
if (!ex || !ex.length) return false;
|
|
2657
|
+
return evalConditions(row, ex, rule.excludeLogic || "AND", curSrc, idx);
|
|
2658
|
+
}
|
|
2659
|
+
// 【P1.2.35】全局排除(按业务源分组的总开关):在所有针对该业务源的规则之前
|
|
2660
|
+
// 先过一道粗过滤——命中则不进任何针对该业务源的规则 rowsIn,且不计入 audit。
|
|
2661
|
+
// 与规则级 excludeWhen 行为一致;但作用范围是"该业务源下所有规则",用于
|
|
2662
|
+
// 「厂牌=资产工具库」「维修工单不挂凭证」这类横扫整张表的统一排除。
|
|
2663
|
+
function isExcludedByGlobals(row, curSrc, idx, globalExcludes) {
|
|
2664
|
+
if (!globalExcludes || typeof globalExcludes !== "object") return false;
|
|
2665
|
+
const cfg = globalExcludes[curSrc];
|
|
2666
|
+
if (!cfg || !cfg.conditions || !cfg.conditions.length) return false;
|
|
2667
|
+
return evalConditions(row, cfg.conditions, cfg.logic || "AND", curSrc, idx);
|
|
2668
|
+
}
|
|
2646
2669
|
function linesKey(lines) {
|
|
2647
2670
|
return JSON.stringify(lines, (k, v) => (k === "_uid" ? undefined : v));
|
|
2648
2671
|
}
|
|
@@ -2677,12 +2700,14 @@ window.WWT_ENGINE = (function () {
|
|
|
2677
2700
|
|
|
2678
2701
|
/* ============================================================
|
|
2679
2702
|
* 主生成:业务行 → 凭证草稿
|
|
2680
|
-
* opts: {rules, biz, monthly, maps, base, selectedIds, modeOverride, groupByOverride, voucherDate, syncBizDate}
|
|
2703
|
+
* opts: {rules, biz, monthly, maps, base, selectedIds, modeOverride, groupByOverride, voucherDate, syncBizDate, globalExcludes}
|
|
2704
|
+
* globalExcludes(【P1.2.35】)= { [srcKey]: { logic, conditions:[...] } },按业务源分组的总开关
|
|
2681
2705
|
* ============================================================ */
|
|
2682
2706
|
function generate(opts) {
|
|
2683
2707
|
const idx = buildIndex(opts.base);
|
|
2684
2708
|
const monthly = opts.monthly || {};
|
|
2685
2709
|
const sel = opts.selectedIds; // Set 或 null(null=全跑)
|
|
2710
|
+
const globalExcludes = opts.globalExcludes || {}; // 【P1.2.35】
|
|
2686
2711
|
let vno = parseInt(monthly.vstart, 10); if (!isFinite(vno)) vno = 1;
|
|
2687
2712
|
let billHead = parseInt(monthly.bstart, 10); if (!isFinite(billHead)) billHead = 1;
|
|
2688
2713
|
const acctDate = clean(opts.voucherDate) || clean(monthly.date);
|
|
@@ -2690,6 +2715,7 @@ window.WWT_ENGINE = (function () {
|
|
|
2690
2715
|
const out = [];
|
|
2691
2716
|
const missing = [];
|
|
2692
2717
|
const usedRows = {}; // src -> Set(index)
|
|
2718
|
+
const globalExcludedUsed = {}; // 【P1.2.35】src -> Set(index),全局排除行不进任何规则 rowsIn
|
|
2693
2719
|
const stats = { rules: 0, rows: 0, vouchers: 0, entries: 0, skipped: [] };
|
|
2694
2720
|
|
|
2695
2721
|
for (const rule of (opts.rules || [])) {
|
|
@@ -2699,12 +2725,20 @@ window.WWT_ENGINE = (function () {
|
|
|
2699
2725
|
const rowsAll = (opts.biz && opts.biz[curSrc]) || [];
|
|
2700
2726
|
if (!rowsAll.length) { stats.skipped.push({ rule: rule.name, why: "无业务数据" }); continue; }
|
|
2701
2727
|
if (!usedRows[curSrc]) usedRows[curSrc] = new Set();
|
|
2728
|
+
if (!globalExcludedUsed[curSrc]) globalExcludedUsed[curSrc] = new Set();
|
|
2702
2729
|
|
|
2703
2730
|
// 命中行(同一行只归属第一条命中的规则)
|
|
2704
2731
|
const rowsIn = [];
|
|
2705
2732
|
for (let i = 0; i < rowsAll.length; i++) {
|
|
2706
2733
|
if (usedRows[curSrc].has(i)) continue;
|
|
2707
2734
|
const row = rowsAll[i];
|
|
2735
|
+
// 【P1.2.35】全局排除(按业务源分组的总开关):命中行不进任何针对该业务源的规则
|
|
2736
|
+
if (isExcludedByGlobals(row, curSrc, idx, globalExcludes)) {
|
|
2737
|
+
globalExcludedUsed[curSrc].add(i);
|
|
2738
|
+
continue;
|
|
2739
|
+
}
|
|
2740
|
+
// 【P1.2.34】规则级排除:满足 excludeWhen 的行整条跳过,不进 rowsIn、不进 audit
|
|
2741
|
+
if (isExcludedByRule(row, rule, curSrc, idx)) continue;
|
|
2708
2742
|
if (evalConditions(row, rule.conditions || [], rule.logic || "AND", curSrc, idx)) {
|
|
2709
2743
|
rowsIn.push({ row: row, _i: i });
|
|
2710
2744
|
}
|
|
@@ -2963,6 +2997,7 @@ window.WWT_ENGINE = (function () {
|
|
|
2963
2997
|
const monthly = opts.monthly || {};
|
|
2964
2998
|
const acctDate = clean(opts.voucherDate) || clean(monthly.date);
|
|
2965
2999
|
const usedRows = new Set(); // 本规则命中的业务行索引集合(Step 2 标记,Step 3 覆盖率审计复用)
|
|
3000
|
+
const globalExcludes = opts.globalExcludes || {}; // 【P1.2.35】
|
|
2966
3001
|
|
|
2967
3002
|
/* ---- Step 1:找出源表"应当处理的行"(candidate):抽 rule 里的「=」条件当候选过滤器。
|
|
2968
3003
|
只在 conditions 顶层 AND 段时按 = 条件过滤;OR 段不参与(避免把 OR 多值都当 AND 求交集)。 */
|
|
@@ -3015,6 +3050,10 @@ window.WWT_ENGINE = (function () {
|
|
|
3015
3050
|
for (let i = 0; i < rowsAll.length; i++) {
|
|
3016
3051
|
if (usedRows.has(i)) continue;
|
|
3017
3052
|
const row = rowsAll[i];
|
|
3053
|
+
// 【P1.2.35】全局排除(按业务源分组的总开关):命中行不进任何 audit 统计
|
|
3054
|
+
if (isExcludedByGlobals(row, curSrc, idx, globalExcludes)) continue;
|
|
3055
|
+
// 【P1.2.34】规则级排除:跳过 excludeWhen 命中的行
|
|
3056
|
+
if (isExcludedByRule(row, r, curSrc, idx)) continue;
|
|
3018
3057
|
if (evalConditions(row, r.conditions || [], r.logic || "AND", curSrc, idx)) {
|
|
3019
3058
|
rowsIn.push({ row: row, _i: i });
|
|
3020
3059
|
}
|
|
@@ -3167,9 +3206,23 @@ window.WWT_ENGINE = (function () {
|
|
|
3167
3206
|
* excludedRows = 源表里不满足本规则的全部 = 条件 → 按规则就不该处理,不算遗漏
|
|
3168
3207
|
* neverMatchedRows = candidatesMissed(兼容旧字段名) */
|
|
3169
3208
|
const candidatesMissed = [], excludedRows = [];
|
|
3209
|
+
let ruleExcludedCount = 0, ruleExcludedAmount = 0;
|
|
3210
|
+
let globalExcludedCount = 0, globalExcludedAmount = 0; // 【P1.2.35】
|
|
3170
3211
|
for (let i = 0; i < rowsAll.length; i++) {
|
|
3171
3212
|
if (matchedIdxs.has(i)) continue;
|
|
3172
3213
|
const row = rowsAll[i];
|
|
3214
|
+
// 【P1.2.35】全局排除:命中行不进任何 audit 统计
|
|
3215
|
+
if (isExcludedByGlobals(row, curSrc, idx, globalExcludes)) {
|
|
3216
|
+
globalExcludedCount++;
|
|
3217
|
+
if (amountCol) globalExcludedAmount += num(row[amountCol]);
|
|
3218
|
+
continue;
|
|
3219
|
+
}
|
|
3220
|
+
// 【P1.2.34】规则级排除:满足 excludeWhen 的行不进任何 audit 统计
|
|
3221
|
+
if (isExcludedByRule(row, rule, curSrc, idx)) {
|
|
3222
|
+
ruleExcludedCount++;
|
|
3223
|
+
if (amountCol) ruleExcludedAmount += num(row[amountCol]);
|
|
3224
|
+
continue;
|
|
3225
|
+
}
|
|
3173
3226
|
const rec = {
|
|
3174
3227
|
原单号: clean(row[meta.no]) || clean(row["原单号"]),
|
|
3175
3228
|
付款方式: clean(row["付款方式"]) || clean(row["支付方式"]),
|
|
@@ -3219,6 +3272,12 @@ window.WWT_ENGINE = (function () {
|
|
|
3219
3272
|
candidate_missed_clipped: candidatesMissed.length >= 200, // 是否被 200 上限截断
|
|
3220
3273
|
excluded: excludedRows.length, // 被本规则排除(不算遗漏)
|
|
3221
3274
|
excluded_clipped: excludedRows.length >= 200,
|
|
3275
|
+
// 【P1.2.34】规则级排除:满足 excludeWhen 的行彻底从 audit 视野消失(业务上合法跳过)
|
|
3276
|
+
rule_excluded: ruleExcludedCount,
|
|
3277
|
+
rule_excluded_amount: round2(ruleExcludedAmount),
|
|
3278
|
+
// 【P1.2.35】全局排除(按业务源分组的总开关):扫表即跳,连候选都不进
|
|
3279
|
+
global_excluded: globalExcludedCount,
|
|
3280
|
+
global_excluded_amount: round2(globalExcludedAmount),
|
|
3222
3281
|
// 兼容旧字段(仍叫 never_matched,但其内容仅含真正应处理子集漏命中的行)
|
|
3223
3282
|
never_matched: candidatesMissed.length,
|
|
3224
3283
|
unmatched_amount: round2(unmatchedAmt),
|
|
@@ -3394,6 +3453,7 @@ window.WWT_ENGINE = (function () {
|
|
|
3394
3453
|
buildIndex, getField, compare, evalConditions, applyTransform,
|
|
3395
3454
|
parseAmtExpr, sumAmtExpr, evalAmtExpr, isAutoDiff, splitFieldsOf,
|
|
3396
3455
|
calcDimValues, leafSubject, resolveEffectiveLines, lookupTypeDim,
|
|
3456
|
+
isExcludedByRule, isExcludedByGlobals,
|
|
3397
3457
|
generate, balanceCheck, buildKingdee, detect, auditRule,
|
|
3398
3458
|
lookupMap, lookupTypeMap,
|
|
3399
3459
|
};
|
|
@@ -3412,13 +3472,13 @@ const nz = v => (v === null || v === undefined || v === "" ? "" : v);
|
|
|
3412
3472
|
|
|
3413
3473
|
/* 全局状态 */
|
|
3414
3474
|
const S = window.__STATE__ = {
|
|
3415
|
-
monthly:{}, maps:{}, rules:[],
|
|
3475
|
+
monthly:{}, maps:{}, rules:[], globalExcludes:{}, // 【P1.2.35】按业务源分组的全局排除(总开关)
|
|
3416
3476
|
base:{}, biz:{}, draft:[], missing:[], balance:null,
|
|
3417
3477
|
selRules:new Set(), cvOut:null, checkRows:null,
|
|
3418
3478
|
};
|
|
3419
3479
|
|
|
3420
3480
|
/* ---------------- 版本 & 框架自动更新(只更新程序本身,本地数据/规则不动) ---------------- */
|
|
3421
|
-
const WWT_VER=(function(){const raw='P1.2.
|
|
3481
|
+
const WWT_VER=(function(){const raw='P1.2.35';return raw.indexOf('__WVER__')>=0?'P1.2.10':raw;})();
|
|
3422
3482
|
const UPD_URL_KEY='wwt_fe_updurl';
|
|
3423
3483
|
// 默认更新源:Gitee 上本项目的 version.json(API 接口,浏览器可直接跨域读)
|
|
3424
3484
|
const DEFAULT_UPD_URL='https://gitee.com/api/v5/repos/wwtmrhu/certificate-generation/contents/dist/version.json';
|
|
@@ -3748,7 +3808,7 @@ function uid(){return 'VR'+Date.now().toString(36)+Math.random().toString(36).sl
|
|
|
3748
3808
|
/* ---------------- 配置(localStorage) ---------------- */
|
|
3749
3809
|
const LS='wwt_fe_cfg_v1';
|
|
3750
3810
|
function saveCfg(){
|
|
3751
|
-
localStorage.setItem(LS,JSON.stringify({monthly:S.monthly,maps:S.maps,rules:S.rules}));
|
|
3811
|
+
localStorage.setItem(LS,JSON.stringify({monthly:S.monthly,maps:S.maps,rules:S.rules,globalExcludes:S.globalExcludes||{}}));
|
|
3752
3812
|
toast('配置已保存到本机');
|
|
3753
3813
|
}
|
|
3754
3814
|
function loadCfg(){
|
|
@@ -3760,12 +3820,15 @@ function loadCfg(){
|
|
|
3760
3820
|
S.monthly=Object.assign({},P.monthly,o.monthly||{});
|
|
3761
3821
|
S.maps=Object.assign({},P.maps,o.maps||{});
|
|
3762
3822
|
S.rules=(o.rules&&o.rules.length)?o.rules:deepCopy(P.rules||[]);
|
|
3823
|
+
// 【P1.2.35】全局排除(按业务源分组的总开关):老存档没这字段 → 给空对象
|
|
3824
|
+
S.globalExcludes=(o.globalExcludes&&typeof o.globalExcludes==='object')?o.globalExcludes:{};
|
|
3763
3825
|
return;
|
|
3764
3826
|
}catch(e){}
|
|
3765
3827
|
}
|
|
3766
3828
|
S.monthly=deepCopy(P.monthly||{});
|
|
3767
3829
|
S.maps=deepCopy(P.maps||{});
|
|
3768
3830
|
S.rules=deepCopy(P.rules||[]);
|
|
3831
|
+
S.globalExcludes={};
|
|
3769
3832
|
}
|
|
3770
3833
|
|
|
3771
3834
|
/* ---------------- Excel 读取(SheetJS,失败自动走兜底) ---------------- */
|
|
@@ -4703,6 +4766,10 @@ function renderRules(){
|
|
|
4703
4766
|
const conds=(r.conditions||[]).map(c=>'<span class="cond"><b>'+esc(c.field)+'</b> '+esc(c.op)+' '+esc(c.value)+'</span>').join('');
|
|
4704
4767
|
let body='<div class="rulebody" id="rb'+i+'">';
|
|
4705
4768
|
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
|
+
}
|
|
4706
4773
|
if((r.branches||[]).length){
|
|
4707
4774
|
body+='<div><b>分支</b>:'+r.branches.map((b,bi)=>
|
|
4708
4775
|
'<div class="linechip"><span class="tag b">分支'+(bi+1)+' '+esc(b.name||'')+'</span>'+
|
|
@@ -4972,6 +5039,18 @@ function ruleFormHTML(){
|
|
|
4972
5039
|
(r.conditions||[]).map((c,i)=>condRowHTML(i,c)).join('')+'</div>'+
|
|
4973
5040
|
'<button class="mini" onclick="UI.addCond()">+ 加条件</button></div>';
|
|
4974
5041
|
|
|
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
|
+
|
|
4975
5054
|
h+='<div class="sec"><h4>分支(可选:按条件走不同分录;命中第一个分支就停)</h4><div id="rf_branches">'+
|
|
4976
5055
|
(r.branches||[]).map((b,bi)=>branchHTML(bi,b)).join('')+'</div>'+
|
|
4977
5056
|
'<button class="mini" onclick="UI.addBranch()">+ 加分支</button></div>';
|
|
@@ -5039,6 +5118,14 @@ function syncFormToObj(){
|
|
|
5039
5118
|
return {field:g('field').value.trim(),op:g('op').value,value:g('value').value.trim(),
|
|
5040
5119
|
logic:g('logic').value||undefined};
|
|
5041
5120
|
});
|
|
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;
|
|
5042
5129
|
// 分支
|
|
5043
5130
|
const bsecs=[...document.querySelectorAll('#rf_branches > [data-bi]')];
|
|
5044
5131
|
r.branches=bsecs.map(sec=>{
|
|
@@ -5092,6 +5179,7 @@ function doGenerate(){
|
|
|
5092
5179
|
selectedIds:sel, modeOverride:$('genMode').value||'',
|
|
5093
5180
|
groupByOverride:$('genGroupBy').value.trim()||'',
|
|
5094
5181
|
voucherDate:date, syncBizDate:$('genSyncBiz').checked,
|
|
5182
|
+
globalExcludes:S.globalExcludes||{}, // 【P1.2.35】
|
|
5095
5183
|
});
|
|
5096
5184
|
}catch(e){ $('genBanner').innerHTML='<div class="banner err"><b>生成出错:</b>'+esc(e.message)+'</div>'; toast('生成失败:'+e.message); return; }
|
|
5097
5185
|
S.draft=res.rows; S.missing=res.missing;
|
|
@@ -5226,6 +5314,7 @@ function previewRule(ruleId){
|
|
|
5226
5314
|
res=E.auditRule({
|
|
5227
5315
|
rule:r, biz:S.biz, base:S.base, maps:S.maps, monthly:S.monthly,
|
|
5228
5316
|
voucherDate:date, syncBizDate:$('genSyncBiz')&&$('genSyncBiz').checked,
|
|
5317
|
+
globalExcludes:S.globalExcludes||{}, // 【P1.2.35】
|
|
5229
5318
|
});
|
|
5230
5319
|
}catch(e){
|
|
5231
5320
|
$('modal').innerHTML='<div class="modalbox"><div class="modalhd"><h3>规则预览失败</h3><button class="mini" onclick="UI.closeModal()">✕ 关闭</button></div>'+
|
|
@@ -5240,6 +5329,7 @@ function previewRule(ruleId){
|
|
|
5240
5329
|
gen=E.generate({
|
|
5241
5330
|
rules:[Object.assign({},r,{enabled:true})], biz:S.biz, base:S.base, maps:S.maps,
|
|
5242
5331
|
monthly:S.monthly, voucherDate:date, syncBizDate:$('genSyncBiz')&&$('genSyncBiz').checked,
|
|
5332
|
+
globalExcludes:S.globalExcludes||{}, // 【P1.2.35】
|
|
5243
5333
|
});
|
|
5244
5334
|
}catch(e){ gen=null; }
|
|
5245
5335
|
_previewCache={rule:r, res:res, gen:gen, date:date};
|
|
@@ -6665,6 +6755,88 @@ window.UI={
|
|
|
6665
6755
|
toggleRuleBody:i=>{const b=$('rb'+i);if(b)b.classList.toggle('on');},
|
|
6666
6756
|
toggleRuleSel:cb=>{cb.checked?S.selRules.add(cb.dataset.rid):S.selRules['delete'](cb.dataset.rid);},
|
|
6667
6757
|
selAllRules:v=>{S.selRules.clear();if(v)S.rules.forEach(r=>S.selRules.add(r.id));renderRuleChecks();},
|
|
6758
|
+
// 【P1.2.35】全局排除弹窗:按业务源分组的总开关(在外面,不嵌在每条规则里)
|
|
6759
|
+
openGlobalExcludes:()=>{
|
|
6760
|
+
const ge = S.globalExcludes || (S.globalExcludes = {});
|
|
6761
|
+
// 从 WWT_CONST.BIZ_SLOTS 拿业务源 key 与显示名
|
|
6762
|
+
const slots = (window.WWT_CONST && window.WWT_CONST.BIZ_SLOTS) || [];
|
|
6763
|
+
let html = '<div class="modalbox" style="max-width:780px;max-height:88vh;display:flex;flex-direction:column">'+
|
|
6764
|
+
'<div class="modalhd"><h3>🚫 全局排除条件(按业务源分组的总开关)</h3>'+
|
|
6765
|
+
'<button class="mini" onclick="UI.closeModal()">✕ 关闭</button></div>'+
|
|
6766
|
+
'<div class="modalbd" style="overflow:auto;flex:1;min-height:0">'+
|
|
6767
|
+
'<div class="banner" style="margin-bottom:10px">'+
|
|
6768
|
+
'<b>用法:</b>按业务源分组配置「命中即跳」的排除条件。<b>满足条件的行不进任何针对该业务源的规则</b>,'+
|
|
6769
|
+
'且不计入覆盖率审计统计(不会被误标成"遗漏")。'+
|
|
6770
|
+
'<br>典型场景:「销售已确认」里厂牌 = <code>资产工具库</code> 的行由其他流程处理,整张表一刀切掉。'+
|
|
6771
|
+
'<br><span style="color:#a40e26">⚠ 全局排除是「粗过滤」,会作用于该业务源下<b>所有规则</b>;想只排除某一条规则请在「编辑规则 → 排除条件」单独配。</span>'+
|
|
6772
|
+
'</div>';
|
|
6773
|
+
// 每个业务源一个折叠 details
|
|
6774
|
+
for (const s of slots) {
|
|
6775
|
+
const cfg = ge[s.key] || (ge[s.key] = { logic: 'AND', conditions: [] });
|
|
6776
|
+
const condHTML = (cfg.conditions || []).map((c, i) => condRowHTML(i, c)).join('');
|
|
6777
|
+
const condsId = 'ge_conds_' + s.key;
|
|
6778
|
+
html += '<details open style="border:1px solid #dde2e8;border-radius:6px;margin:6px 0;background:#fff">'+
|
|
6779
|
+
'<summary style="padding:8px 12px;cursor:pointer;font-weight:600">'+
|
|
6780
|
+
'<span style="display:inline-block;width:160px">'+esc(s.label)+'</span>'+
|
|
6781
|
+
'<span style="color:#a40e26;font-size:12px">['+((cfg.conditions||[]).length)+' 个条件]</span>'+
|
|
6782
|
+
'</summary>'+
|
|
6783
|
+
'<div style="padding:4px 12px 12px">'+
|
|
6784
|
+
'<div class="row" style="margin-bottom:6px">'+
|
|
6785
|
+
'<span class="w80">逻辑</span>'+
|
|
6786
|
+
'<select class="w80" data-ge="logic" data-src="'+esc(s.key)+'">'+
|
|
6787
|
+
'<option value="AND"'+((cfg.logic||'AND')==='AND'?' selected':'')+'>AND(全部满足)</option>'+
|
|
6788
|
+
'<option value="OR"'+(cfg.logic==='OR'?' selected':'')+'>OR(任一满足)</option>'+
|
|
6789
|
+
'</select>'+
|
|
6790
|
+
'<span style="margin-left:12px;font-size:11.5px;color:#888">满足 = 该行被排除(不参与任何规则、不进 audit)</span>'+
|
|
6791
|
+
'</div>'+
|
|
6792
|
+
'<div class="condrows" id="'+condsId+'" data-src="'+esc(s.key)+'">'+condHTML+'</div>'+
|
|
6793
|
+
'<button class="mini" onclick="UI.addGlobalExcludeCond(\''+s.key+'\')">+ 加条件</button>'+
|
|
6794
|
+
'</div></details>';
|
|
6795
|
+
}
|
|
6796
|
+
html += '</div>'+
|
|
6797
|
+
'<div class="modalft">'+
|
|
6798
|
+
'<button class="btn ghost" onclick="UI.closeModal()">取消</button>'+
|
|
6799
|
+
'<button class="btn primary" onclick="UI.saveGlobalExcludes()">💾 保存全局排除</button>'+
|
|
6800
|
+
'</div></div>';
|
|
6801
|
+
const m = $('modal'); m.innerHTML = html; m.style.display = 'flex';
|
|
6802
|
+
},
|
|
6803
|
+
// 在某个业务源分组下加一行条件
|
|
6804
|
+
addGlobalExcludeCond:(srcKey)=>{
|
|
6805
|
+
const ge = S.globalExcludes || (S.globalExcludes = {});
|
|
6806
|
+
if (!ge[srcKey]) ge[srcKey] = { logic: 'AND', conditions: [] };
|
|
6807
|
+
ge[srcKey].conditions.push({ field: '', op: '=', value: '', logic: '' });
|
|
6808
|
+
UI.openGlobalExcludes(); // 直接重渲染(简单实现;数据在 S.globalExcludes 里有真实存储)
|
|
6809
|
+
},
|
|
6810
|
+
// 收集弹窗里的输入,写回 S.globalExcludes 并 saveCfg
|
|
6811
|
+
saveGlobalExcludes:()=>{
|
|
6812
|
+
const ge = S.globalExcludes || (S.globalExcludes = {});
|
|
6813
|
+
// 收集每个业务源的 logic + conditions
|
|
6814
|
+
document.querySelectorAll('[data-ge="logic"][data-src]').forEach(sel=>{
|
|
6815
|
+
const src = sel.getAttribute('data-src');
|
|
6816
|
+
if (!ge[src]) ge[src] = { logic: 'AND', conditions: [] };
|
|
6817
|
+
ge[src].logic = sel.value || 'AND';
|
|
6818
|
+
});
|
|
6819
|
+
document.querySelectorAll('.condrows[data-src]').forEach(box=>{
|
|
6820
|
+
const src = box.getAttribute('data-src');
|
|
6821
|
+
if (!ge[src]) ge[src] = { logic: 'AND', conditions: [] };
|
|
6822
|
+
const rows = [...box.querySelectorAll(':scope > .condrow')].map(row => {
|
|
6823
|
+
const f = (row.querySelector('[data-f="field"]') || {}).value || '';
|
|
6824
|
+
const op = (row.querySelector('[data-f="op"]') || {}).value || '=';
|
|
6825
|
+
const v = (row.querySelector('[data-f="value"]') || {}).value || '';
|
|
6826
|
+
const lg = (row.querySelector('[data-f="logic"]')|| {}).value || '';
|
|
6827
|
+
return { field: f, op: op, value: v, logic: lg };
|
|
6828
|
+
}).filter(c => c.field && c.op); // 空条件过滤掉
|
|
6829
|
+
ge[src].conditions = rows;
|
|
6830
|
+
// 没有任何有效条件 → 整个 srcKey 配置删掉(保持 cfg 干净)
|
|
6831
|
+
if (!rows.length) delete ge[src];
|
|
6832
|
+
});
|
|
6833
|
+
saveCfg();
|
|
6834
|
+
UI.closeModal();
|
|
6835
|
+
const totalSrc = Object.keys(ge).filter(k => (ge[k].conditions||[]).length).length;
|
|
6836
|
+
const totalCond = Object.values(ge).reduce((s, x) => s + ((x && x.conditions) ? x.conditions.length : 0), 0);
|
|
6837
|
+
toast('已保存全局排除:'+totalSrc+' 个业务源 / '+totalCond+' 条条件');
|
|
6838
|
+
},
|
|
6839
|
+
|
|
6668
6840
|
// 规则编辑器
|
|
6669
6841
|
closeModal:()=>{$('modal').style.display='none';editing=null;editingIdx=-1;renderRules();},
|
|
6670
6842
|
saveRuleForm:()=>{
|
|
@@ -6680,6 +6852,13 @@ window.UI={
|
|
|
6680
6852
|
else editing.conditions.push({field:'',op:'=',value:'',logic:''});
|
|
6681
6853
|
redrawRuleForm();
|
|
6682
6854
|
},
|
|
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
|
+
},
|
|
6683
6862
|
addBranch:()=>{
|
|
6684
6863
|
syncFormToObj();
|
|
6685
6864
|
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.35",
|
|
3
|
+
"npm": "1.2.35",
|
|
4
|
+
"url": "https://cdn.jsdelivr.net/npm/wwt-certificate-tool@1.2.35/dist/app.html",
|
|
5
|
+
"url_unpkg": "https://unpkg.com/wwt-certificate-tool@1.2.35/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.35.html",
|
|
7
|
+
"note": "①「全局排除」机制升级:把排除条件从每条规则里挪出来,做成外面工具栏的「🚫 全局排除(总开关)」按钮,按业务源分组配置(折叠面板)。命中行不进任何针对该业务源的规则,且不计入审计统计。典型场景:「销售已确认」里厂牌=资产工具库 的行整张表一刀切掉。② cfg 新增 globalExcludes 字段(saveCfg/loadCfg 自动读写);引擎新增 isExcludedByGlobals(),generate/auditRule 三处接入。③ by_source 返回值新增 global_excluded/global_excluded_amount 字段(与 P1.2.34 的 rule_excluded 并列)。④ 单规则的 rule.excludeWhen 保留作为兜底细粒度配置。⑤ 工具栏新增橙底按钮 + 新增 .btn.warn 样式;弹窗复用 condRowHTML 条件行模板。回归 202/202 全通过。"
|
|
8
8
|
}
|