wwt-certificate-tool 1.2.34 → 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 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.34</title>
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.34</span></h1>
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">规则改动要点「保存所有规则」才会生效</span>
380
+ <span class="tip">规则改动要点「保存所有规则」才会生效;全局排除在弹窗里保存后即生效(也写入本地存档)</span>
378
381
  </div>
379
382
  <div id="ruleTabs" class="src-tabs"></div>
380
383
  <div id="ruleList"></div>
@@ -2653,6 +2656,16 @@ window.WWT_ENGINE = (function () {
2653
2656
  if (!ex || !ex.length) return false;
2654
2657
  return evalConditions(row, ex, rule.excludeLogic || "AND", curSrc, idx);
2655
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
+ }
2656
2669
  function linesKey(lines) {
2657
2670
  return JSON.stringify(lines, (k, v) => (k === "_uid" ? undefined : v));
2658
2671
  }
@@ -2687,12 +2700,14 @@ window.WWT_ENGINE = (function () {
2687
2700
 
2688
2701
  /* ============================================================
2689
2702
  * 主生成:业务行 → 凭证草稿
2690
- * 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:[...] } },按业务源分组的总开关
2691
2705
  * ============================================================ */
2692
2706
  function generate(opts) {
2693
2707
  const idx = buildIndex(opts.base);
2694
2708
  const monthly = opts.monthly || {};
2695
2709
  const sel = opts.selectedIds; // Set 或 null(null=全跑)
2710
+ const globalExcludes = opts.globalExcludes || {}; // 【P1.2.35】
2696
2711
  let vno = parseInt(monthly.vstart, 10); if (!isFinite(vno)) vno = 1;
2697
2712
  let billHead = parseInt(monthly.bstart, 10); if (!isFinite(billHead)) billHead = 1;
2698
2713
  const acctDate = clean(opts.voucherDate) || clean(monthly.date);
@@ -2700,6 +2715,7 @@ window.WWT_ENGINE = (function () {
2700
2715
  const out = [];
2701
2716
  const missing = [];
2702
2717
  const usedRows = {}; // src -> Set(index)
2718
+ const globalExcludedUsed = {}; // 【P1.2.35】src -> Set(index),全局排除行不进任何规则 rowsIn
2703
2719
  const stats = { rules: 0, rows: 0, vouchers: 0, entries: 0, skipped: [] };
2704
2720
 
2705
2721
  for (const rule of (opts.rules || [])) {
@@ -2709,12 +2725,18 @@ window.WWT_ENGINE = (function () {
2709
2725
  const rowsAll = (opts.biz && opts.biz[curSrc]) || [];
2710
2726
  if (!rowsAll.length) { stats.skipped.push({ rule: rule.name, why: "无业务数据" }); continue; }
2711
2727
  if (!usedRows[curSrc]) usedRows[curSrc] = new Set();
2728
+ if (!globalExcludedUsed[curSrc]) globalExcludedUsed[curSrc] = new Set();
2712
2729
 
2713
2730
  // 命中行(同一行只归属第一条命中的规则)
2714
2731
  const rowsIn = [];
2715
2732
  for (let i = 0; i < rowsAll.length; i++) {
2716
2733
  if (usedRows[curSrc].has(i)) continue;
2717
2734
  const row = rowsAll[i];
2735
+ // 【P1.2.35】全局排除(按业务源分组的总开关):命中行不进任何针对该业务源的规则
2736
+ if (isExcludedByGlobals(row, curSrc, idx, globalExcludes)) {
2737
+ globalExcludedUsed[curSrc].add(i);
2738
+ continue;
2739
+ }
2718
2740
  // 【P1.2.34】规则级排除:满足 excludeWhen 的行整条跳过,不进 rowsIn、不进 audit
2719
2741
  if (isExcludedByRule(row, rule, curSrc, idx)) continue;
2720
2742
  if (evalConditions(row, rule.conditions || [], rule.logic || "AND", curSrc, idx)) {
@@ -2975,6 +2997,7 @@ window.WWT_ENGINE = (function () {
2975
2997
  const monthly = opts.monthly || {};
2976
2998
  const acctDate = clean(opts.voucherDate) || clean(monthly.date);
2977
2999
  const usedRows = new Set(); // 本规则命中的业务行索引集合(Step 2 标记,Step 3 覆盖率审计复用)
3000
+ const globalExcludes = opts.globalExcludes || {}; // 【P1.2.35】
2978
3001
 
2979
3002
  /* ---- Step 1:找出源表"应当处理的行"(candidate):抽 rule 里的「=」条件当候选过滤器。
2980
3003
  只在 conditions 顶层 AND 段时按 = 条件过滤;OR 段不参与(避免把 OR 多值都当 AND 求交集)。 */
@@ -3027,6 +3050,8 @@ window.WWT_ENGINE = (function () {
3027
3050
  for (let i = 0; i < rowsAll.length; i++) {
3028
3051
  if (usedRows.has(i)) continue;
3029
3052
  const row = rowsAll[i];
3053
+ // 【P1.2.35】全局排除(按业务源分组的总开关):命中行不进任何 audit 统计
3054
+ if (isExcludedByGlobals(row, curSrc, idx, globalExcludes)) continue;
3030
3055
  // 【P1.2.34】规则级排除:跳过 excludeWhen 命中的行
3031
3056
  if (isExcludedByRule(row, r, curSrc, idx)) continue;
3032
3057
  if (evalConditions(row, r.conditions || [], r.logic || "AND", curSrc, idx)) {
@@ -3182,9 +3207,16 @@ window.WWT_ENGINE = (function () {
3182
3207
  * neverMatchedRows = candidatesMissed(兼容旧字段名) */
3183
3208
  const candidatesMissed = [], excludedRows = [];
3184
3209
  let ruleExcludedCount = 0, ruleExcludedAmount = 0;
3210
+ let globalExcludedCount = 0, globalExcludedAmount = 0; // 【P1.2.35】
3185
3211
  for (let i = 0; i < rowsAll.length; i++) {
3186
3212
  if (matchedIdxs.has(i)) continue;
3187
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
+ }
3188
3220
  // 【P1.2.34】规则级排除:满足 excludeWhen 的行不进任何 audit 统计
3189
3221
  if (isExcludedByRule(row, rule, curSrc, idx)) {
3190
3222
  ruleExcludedCount++;
@@ -3243,6 +3275,9 @@ window.WWT_ENGINE = (function () {
3243
3275
  // 【P1.2.34】规则级排除:满足 excludeWhen 的行彻底从 audit 视野消失(业务上合法跳过)
3244
3276
  rule_excluded: ruleExcludedCount,
3245
3277
  rule_excluded_amount: round2(ruleExcludedAmount),
3278
+ // 【P1.2.35】全局排除(按业务源分组的总开关):扫表即跳,连候选都不进
3279
+ global_excluded: globalExcludedCount,
3280
+ global_excluded_amount: round2(globalExcludedAmount),
3246
3281
  // 兼容旧字段(仍叫 never_matched,但其内容仅含真正应处理子集漏命中的行)
3247
3282
  never_matched: candidatesMissed.length,
3248
3283
  unmatched_amount: round2(unmatchedAmt),
@@ -3418,7 +3453,7 @@ window.WWT_ENGINE = (function () {
3418
3453
  buildIndex, getField, compare, evalConditions, applyTransform,
3419
3454
  parseAmtExpr, sumAmtExpr, evalAmtExpr, isAutoDiff, splitFieldsOf,
3420
3455
  calcDimValues, leafSubject, resolveEffectiveLines, lookupTypeDim,
3421
- isExcludedByRule,
3456
+ isExcludedByRule, isExcludedByGlobals,
3422
3457
  generate, balanceCheck, buildKingdee, detect, auditRule,
3423
3458
  lookupMap, lookupTypeMap,
3424
3459
  };
@@ -3437,13 +3472,13 @@ const nz = v => (v === null || v === undefined || v === "" ? "" : v);
3437
3472
 
3438
3473
  /* 全局状态 */
3439
3474
  const S = window.__STATE__ = {
3440
- monthly:{}, maps:{}, rules:[],
3475
+ monthly:{}, maps:{}, rules:[], globalExcludes:{}, // 【P1.2.35】按业务源分组的全局排除(总开关)
3441
3476
  base:{}, biz:{}, draft:[], missing:[], balance:null,
3442
3477
  selRules:new Set(), cvOut:null, checkRows:null,
3443
3478
  };
3444
3479
 
3445
3480
  /* ---------------- 版本 & 框架自动更新(只更新程序本身,本地数据/规则不动) ---------------- */
3446
- const WWT_VER=(function(){const raw='P1.2.34';return raw.indexOf('__WVER__')>=0?'P1.2.10':raw;})();
3481
+ const WWT_VER=(function(){const raw='P1.2.35';return raw.indexOf('__WVER__')>=0?'P1.2.10':raw;})();
3447
3482
  const UPD_URL_KEY='wwt_fe_updurl';
3448
3483
  // 默认更新源:Gitee 上本项目的 version.json(API 接口,浏览器可直接跨域读)
3449
3484
  const DEFAULT_UPD_URL='https://gitee.com/api/v5/repos/wwtmrhu/certificate-generation/contents/dist/version.json';
@@ -3773,7 +3808,7 @@ function uid(){return 'VR'+Date.now().toString(36)+Math.random().toString(36).sl
3773
3808
  /* ---------------- 配置(localStorage) ---------------- */
3774
3809
  const LS='wwt_fe_cfg_v1';
3775
3810
  function saveCfg(){
3776
- 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||{}}));
3777
3812
  toast('配置已保存到本机');
3778
3813
  }
3779
3814
  function loadCfg(){
@@ -3785,12 +3820,15 @@ function loadCfg(){
3785
3820
  S.monthly=Object.assign({},P.monthly,o.monthly||{});
3786
3821
  S.maps=Object.assign({},P.maps,o.maps||{});
3787
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:{};
3788
3825
  return;
3789
3826
  }catch(e){}
3790
3827
  }
3791
3828
  S.monthly=deepCopy(P.monthly||{});
3792
3829
  S.maps=deepCopy(P.maps||{});
3793
3830
  S.rules=deepCopy(P.rules||[]);
3831
+ S.globalExcludes={};
3794
3832
  }
3795
3833
 
3796
3834
  /* ---------------- Excel 读取(SheetJS,失败自动走兜底) ---------------- */
@@ -5141,6 +5179,7 @@ function doGenerate(){
5141
5179
  selectedIds:sel, modeOverride:$('genMode').value||'',
5142
5180
  groupByOverride:$('genGroupBy').value.trim()||'',
5143
5181
  voucherDate:date, syncBizDate:$('genSyncBiz').checked,
5182
+ globalExcludes:S.globalExcludes||{}, // 【P1.2.35】
5144
5183
  });
5145
5184
  }catch(e){ $('genBanner').innerHTML='<div class="banner err"><b>生成出错:</b>'+esc(e.message)+'</div>'; toast('生成失败:'+e.message); return; }
5146
5185
  S.draft=res.rows; S.missing=res.missing;
@@ -5275,6 +5314,7 @@ function previewRule(ruleId){
5275
5314
  res=E.auditRule({
5276
5315
  rule:r, biz:S.biz, base:S.base, maps:S.maps, monthly:S.monthly,
5277
5316
  voucherDate:date, syncBizDate:$('genSyncBiz')&&$('genSyncBiz').checked,
5317
+ globalExcludes:S.globalExcludes||{}, // 【P1.2.35】
5278
5318
  });
5279
5319
  }catch(e){
5280
5320
  $('modal').innerHTML='<div class="modalbox"><div class="modalhd"><h3>规则预览失败</h3><button class="mini" onclick="UI.closeModal()">✕ 关闭</button></div>'+
@@ -5289,6 +5329,7 @@ function previewRule(ruleId){
5289
5329
  gen=E.generate({
5290
5330
  rules:[Object.assign({},r,{enabled:true})], biz:S.biz, base:S.base, maps:S.maps,
5291
5331
  monthly:S.monthly, voucherDate:date, syncBizDate:$('genSyncBiz')&&$('genSyncBiz').checked,
5332
+ globalExcludes:S.globalExcludes||{}, // 【P1.2.35】
5292
5333
  });
5293
5334
  }catch(e){ gen=null; }
5294
5335
  _previewCache={rule:r, res:res, gen:gen, date:date};
@@ -6714,6 +6755,88 @@ window.UI={
6714
6755
  toggleRuleBody:i=>{const b=$('rb'+i);if(b)b.classList.toggle('on');},
6715
6756
  toggleRuleSel:cb=>{cb.checked?S.selRules.add(cb.dataset.rid):S.selRules['delete'](cb.dataset.rid);},
6716
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
+
6717
6840
  // 规则编辑器
6718
6841
  closeModal:()=>{$('modal').style.display='none';editing=null;editingIdx=-1;renderRules();},
6719
6842
  saveRuleForm:()=>{
package/dist/version.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
- "ver": "P1.2.34",
3
- "npm": "1.2.34",
4
- "url": "https://cdn.jsdelivr.net/npm/wwt-certificate-tool@1.2.34/dist/app.html",
5
- "url_unpkg": "https://unpkg.com/wwt-certificate-tool@1.2.34/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.34.html",
7
- "note": " 新增「规则级排除」机制:规则上加 excludeWhen 字段(结构同 conditions,含 excludeLogic),满足条件的行整条规则不处理,**且不进覆盖率审计**(不进 excluded_rows、不进 candidate_missed、不进任何 audit 统计);用于「厂牌=资产工具库」等由其他流程直接处理的合法跳过场景。② UI:规则编辑面板增加「排除条件」独立配置区(橙底强调),规则列表同步显示摘要;「+ 加排除条件」按钮一键加。③ auditRule 返回值新增 rule_excluded/rule_excluded_amount 字段(供后续 UI 展示)。例:「厂牌 = 资产工具库」时整条不处理、不计入审计。"
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wwt-certificate-tool",
3
- "version": "1.2.34",
3
+ "version": "1.2.35",
4
4
  "description": "WWT 金蝶凭证工具 · 纯前端版(离线单文件 HTML,双击即用)",
5
5
  "keywords": [
6
6
  "WWT",