wwt-certificate-tool 1.2.59 → 1.2.61

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.59</title>
6
+ <title>WWT 金蝶凭证工具 · 纯前端版 P1.2.61</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}
@@ -183,6 +183,7 @@ tr.bad:hover td{background:#fbe9e9}
183
183
  .chkbox.generated{background:#dcfce7;border-color:#86efac;color:#166534}
184
184
  .chkbox.generated:hover{background:#bbf7d0}
185
185
  .genbadge{margin-left:4px;background:#22c55e;color:#fff;font-size:10px;font-weight:600;border-radius:4px;padding:1px 5px}
186
+ .vnobadge{margin-left:4px;background:#f43f5e;color:#fff;font-size:10px;font-weight:600;border-radius:4px;padding:1px 5px}
186
187
  .rulecard{background:#fff;border:1px solid #dde2e8;border-radius:7px;padding:11px 13px;margin-bottom:9px}
187
188
  .rulecard.off{opacity:.55}
188
189
  .rulehead{display:flex;justify-content:space-between;align-items:center;gap:10px;flex-wrap:wrap}
@@ -320,7 +321,7 @@ input[data-f="field"][list^="dlGe_"]::placeholder{color:#a8861f;font-style:itali
320
321
  <div class="brand">
321
322
  <span class="logo">WWT</span>
322
323
  <div>
323
- <h1>金蝶凭证工具 <span class="ver">P1.2.59</span></h1>
324
+ <h1>金蝶凭证工具 <span class="ver">P1.2.61</span></h1>
324
325
  <div class="sub">纯前端离线版 · 双击即用 · 数据只存在本机,不上传任何服务器</div>
325
326
  </div>
326
327
  </div>
@@ -1731,7 +1732,6 @@ window.WWT_ENGINE = (function () {
1731
1732
  const monthly = opts.monthly || {};
1732
1733
  const sel = opts.selectedIds; // Set 或 null(null=全跑)
1733
1734
  const globalExcludes = opts.globalExcludes || {}; // 【P1.2.35】
1734
- let vno = parseInt(monthly.vstart, 10); if (!isFinite(vno)) vno = 1;
1735
1735
  let billHead = parseInt(monthly.bstart, 10); if (!isFinite(billHead)) billHead = 1;
1736
1736
  const acctDate = clean(opts.voucherDate) || clean(monthly.date);
1737
1737
 
@@ -1740,6 +1740,22 @@ window.WWT_ENGINE = (function () {
1740
1740
  const usedRows = {}; // src -> Set(index)
1741
1741
  const globalExcludedUsed = {}; // 【P1.2.35】src -> Set(index),全局排除行不进任何规则 rowsIn
1742
1742
  const stats = { rules: 0, rows: 0, vouchers: 0, entries: 0, skipped: [], totalRows: 0 };
1743
+ const ruleVnos = {}; // rule.id -> {min,max} 本次生成的凭证号范围
1744
+
1745
+ // 【P1.2.61】保留已有凭证号:从 UI 传入的 ruleVnos 里建立“号段占用表”,
1746
+ // 避免第二次生成时把新规则塞到旧规则已经占用的号段里。
1747
+ const existingVnos = opts.ruleVnos || {};
1748
+ const reserved = new Map(); // vno -> ruleId,记录各规则已占用的凭证号
1749
+ let maxExisting = 0;
1750
+ for (const id in existingVnos) {
1751
+ const rv = existingVnos[id];
1752
+ if (!rv || !isFinite(rv.min)) continue;
1753
+ const mx = isFinite(rv.max) ? rv.max : rv.min;
1754
+ if (mx > maxExisting) maxExisting = mx;
1755
+ for (let n = rv.min; n <= mx; n++) reserved.set(n, id);
1756
+ }
1757
+ let vno = parseInt(monthly.vstart, 10); if (!isFinite(vno)) vno = 1;
1758
+ vno = Math.max(vno, maxExisting + 1);
1743
1759
 
1744
1760
  for (const rule of (opts.rules || [])) {
1745
1761
  if (rule.enabled === false) continue;
@@ -1806,9 +1822,26 @@ window.WWT_ENGINE = (function () {
1806
1822
  }
1807
1823
 
1808
1824
  const splitFs = splitFieldsOf(rule);
1809
-
1810
- for (const bucket of buckets) {
1811
- const curVno = vno++;
1825
+ const rv = existingVnos[rule.id];
1826
+
1827
+ for (let bi = 0; bi < buckets.length; bi++) {
1828
+ const bucket = buckets[bi];
1829
+ let curVno;
1830
+ if (rv && isFinite(rv.min)) {
1831
+ // 优先复用本规则原来的号;若该号被其它规则占用,则顺延到下一个可用号
1832
+ const intended = rv.min + bi;
1833
+ const owner = reserved.get(intended);
1834
+ if (!owner || owner === rule.id) {
1835
+ curVno = intended;
1836
+ if (curVno >= vno) vno = curVno + 1;
1837
+ } else {
1838
+ curVno = vno++;
1839
+ }
1840
+ } else {
1841
+ curVno = vno++;
1842
+ }
1843
+ if (!ruleVnos[rule.id]) ruleVnos[rule.id] = { min: curVno, max: curVno };
1844
+ else ruleVnos[rule.id].max = Math.max(ruleVnos[rule.id].max, curVno);
1812
1845
  const curBill = billHead++;
1813
1846
  let eidx = 0;
1814
1847
  stats.vouchers++;
@@ -2001,7 +2034,7 @@ window.WWT_ENGINE = (function () {
2001
2034
  never_matched_rows: exclRows,
2002
2035
  });
2003
2036
  }
2004
- return { rows: out, missing: missing, stats: stats, index: idx, audit: { by_source: bySource } };
2037
+ return { rows: out, missing: missing, stats: stats, index: idx, audit: { by_source: bySource }, ruleVnos: ruleVnos };
2005
2038
  }
2006
2039
 
2007
2040
  /* ---------------- 借贷平衡校验(按凭证号分组,容差 0.005) ---------------- */
@@ -2584,10 +2617,11 @@ const S = window.__STATE__ = {
2584
2617
  base:{}, biz:{}, draft:[], missing:[], balance:null,
2585
2618
  selRules:new Set(), cvOut:null, checkRows:null,
2586
2619
  genRules:new Set(), // 【P1.2.50】已生成过凭证的规则 id,持久化到 localStorage
2620
+ ruleVnos:{}, // 【P1.2.60】每条规则上次生成的凭证号范围
2587
2621
  };
2588
2622
 
2589
2623
  /* ---------------- 版本 & 框架自动更新(只更新程序本身,本地数据/规则不动) ---------------- */
2590
- const WWT_VER=(function(){const raw='P1.2.59';return raw.indexOf('__WVER__')>=0?'P1.2.10':raw;})();
2624
+ const WWT_VER=(function(){const raw='P1.2.61';return raw.indexOf('__WVER__')>=0?'P1.2.10':raw;})();
2591
2625
  const UPD_URL_KEY='wwt_fe_updurl';
2592
2626
  // 默认更新源:Gitee 上本项目的 version.json(API 接口,浏览器可直接跨域读)
2593
2627
  const DEFAULT_UPD_URL='https://gitee.com/api/v5/repos/wwtmrhu/certificate-generation/contents/dist/version.json';
@@ -2918,7 +2952,7 @@ function uid(){return 'VR'+Date.now().toString(36)+Math.random().toString(36).sl
2918
2952
  const LS='wwt_fe_cfg_v1';
2919
2953
  function saveCfg(){
2920
2954
  localStorage.setItem(LS,JSON.stringify({monthly:S.monthly,maps:S.maps,rules:S.rules,globalExcludes:S.globalExcludes||{},
2921
- generatedRuleIds:Array.from(S.genRules||[])}));
2955
+ generatedRuleIds:Array.from(S.genRules||[]), ruleVnos:S.ruleVnos||{}}));
2922
2956
  toast('配置已保存到本机');
2923
2957
  }
2924
2958
  function loadCfg(){
@@ -2934,6 +2968,8 @@ function loadCfg(){
2934
2968
  S.globalExcludes=(o.globalExcludes&&typeof o.globalExcludes==='object')?o.globalExcludes:{};
2935
2969
  // 【P1.2.50】已生成凭证的规则状态持久化
2936
2970
  S.genRules=new Set(o.generatedRuleIds||[]);
2971
+ // 【P1.2.60】规则生成的凭证号范围持久化
2972
+ S.ruleVnos=(o.ruleVnos&&typeof o.ruleVnos==='object')?o.ruleVnos:{};
2937
2973
  return;
2938
2974
  }catch(e){}
2939
2975
  }
@@ -2942,6 +2978,7 @@ function loadCfg(){
2942
2978
  S.rules=deepCopy(P.rules||[]);
2943
2979
  S.globalExcludes={};
2944
2980
  S.genRules=new Set();
2981
+ S.ruleVnos={};
2945
2982
  }
2946
2983
 
2947
2984
  /* ---------------- Excel 读取(SheetJS,失败自动走兜底) ---------------- */
@@ -3915,9 +3952,15 @@ function renderRuleChecks(){
3915
3952
  const list=filter==='all'?S.rules:S.rules.filter(r=>r.source===filter);
3916
3953
  $('ruleChecks').innerHTML=list.length?list.map(r=>{
3917
3954
  const generated=S.genRules.has(r.id);
3955
+ const vnoInfo=S.ruleVnos[r.id];
3956
+ let vnoBadge='';
3957
+ if(vnoInfo){
3958
+ const vtxt=vnoInfo.min===vnoInfo.max?String(vnoInfo.min):vnoInfo.min+'-'+vnoInfo.max;
3959
+ vnoBadge='<span class="vnobadge" title="本规则生成的凭证号">凭证 '+esc(vtxt)+'</span>';
3960
+ }
3918
3961
  return '<label class="chkbox'+(generated?' generated':'')+'"><input type="checkbox" data-rid="'+esc(r.id)+'" '+
3919
3962
  (S.selRules.has(r.id)?'checked':'')+' onchange="UI.toggleRuleSel(this)">'+
3920
- esc(r.name||r.id)+(generated?'<span class="genbadge">已生成</span>':'')+
3963
+ esc(r.name||r.id)+(generated?'<span class="genbadge">已生成</span>':'')+vnoBadge+
3921
3964
  '<span style="color:#8b949e;font-size:11px">'+esc(r.source)+'</span></label>';
3922
3965
  }).join('')
3923
3966
  :'<div style="color:#8b949e;font-size:12px;padding:8px">当前来源「'+esc(filter)+'」没规则可勾选。切到「全部」或在「③ 规则配置」加规则。</div>';
@@ -4267,6 +4310,41 @@ function redrawRuleForm(){
4267
4310
  /* ============================================================
4268
4311
  * ④ 生成凭证
4269
4312
  * ============================================================ */
4313
+
4314
+ // 【P1.2.60】多规则全选生成时,按以下优先级排序,让同类/同付款方式规则的凭证号相邻
4315
+ const GEN_PRIORITY_SOURCES = ['入库未确认','出库未确认','调拨未确认','在修车','已完工未结算'];
4316
+ const GEN_PRIORITY_PAYMENTS = ['对冲F','对账S','对账F','还件F','入库F','冲转S','冲转F','挂账','冲预收','冲预付','现金S','银行S','现金F','银行F'];
4317
+ function _uClean(s){ return String(s==null?'':s).trim(); }
4318
+ function detectRulePayment(r){
4319
+ const conds=r.conditions||[];
4320
+ for(const c of conds){
4321
+ const f=_uClean(c.field||'');
4322
+ if(/付款方式|支付方式|结算方式/.test(f)){
4323
+ const v=_uClean(c.value||'');
4324
+ for(const p of GEN_PRIORITY_PAYMENTS) if(v.indexOf(p)>=0) return p;
4325
+ }
4326
+ }
4327
+ const name=_uClean(r.name||'');
4328
+ for(const p of GEN_PRIORITY_PAYMENTS) if(name.indexOf(p)>=0) return p;
4329
+ return '';
4330
+ }
4331
+ function rulePriorityKey(r){
4332
+ const src=_uClean(r.source||'');
4333
+ for(let i=0;i<GEN_PRIORITY_SOURCES.length;i++) if(src.indexOf(GEN_PRIORITY_SOURCES[i])>=0) return [0,i];
4334
+ const pay=detectRulePayment(r);
4335
+ const pi=GEN_PRIORITY_PAYMENTS.indexOf(pay);
4336
+ if(pi>=0) return [1,pi];
4337
+ return [2,0];
4338
+ }
4339
+ function sortRulesForGen(rules){
4340
+ return rules.slice().sort((a,b)=>{
4341
+ const ka=rulePriorityKey(a), kb=rulePriorityKey(b);
4342
+ for(let i=0;i<ka.length;i++){ if(ka[i]!==kb[i]) return ka[i]-kb[i]; }
4343
+ return 0;
4344
+ });
4345
+ }
4346
+ window.WWT_RULE_PRIO={detectRulePayment, rulePriorityKey, sortRulesForGen};
4347
+
4270
4348
  function doGenerate(){
4271
4349
  // 【P1.2.51】生成栏里的「凭证起始号」优先级最高:生成前把它写回 S.monthly,并同步到②映射页的输入框
4272
4350
  const vsInp=$('genVStart');
@@ -4284,21 +4362,28 @@ function doGenerate(){
4284
4362
  const date=$('genDate').value || S.monthly.date;
4285
4363
  if(!date){ toast('先填凭证日期(或在月度参数里填记账日期)'); return; }
4286
4364
  const sel=S.selRules.size?S.selRules:null;
4365
+ // 【P1.2.60】多规则生成时按优先级排序;单条生成时保持原顺序
4366
+ let genRules=S.rules.slice();
4367
+ if(sel) genRules=genRules.filter(r=>sel.has(r.id));
4368
+ if(genRules.length>1) genRules=sortRulesForGen(genRules);
4287
4369
  const t0=performance.now();
4288
4370
  let res;
4289
4371
  try{
4290
4372
  res=E.generate({
4291
- rules:S.rules, biz:S.biz, monthly:S.monthly, maps:S.maps, base:S.base,
4373
+ rules:genRules, biz:S.biz, monthly:S.monthly, maps:S.maps, base:S.base,
4292
4374
  selectedIds:sel, modeOverride:$('genMode').value||'',
4293
4375
  groupByOverride:$('genGroupBy').value.trim()||'',
4294
4376
  voucherDate:date, syncBizDate:$('genSyncBiz').checked,
4295
4377
  globalExcludes:S.globalExcludes||{}, // 【P1.2.35】
4378
+ ruleVnos:S.ruleVnos||{}, // 【P1.2.61】传入已有凭证号,避免重复分配
4296
4379
  });
4297
4380
  }catch(e){ $('genBanner').innerHTML='<div class="banner err"><b>生成出错:</b>'+esc(e.message)+'</div>'; toast('生成失败:'+e.message); return; }
4298
4381
  S.draft=res.rows; S.missing=res.missing;
4299
4382
  S.balance=E.balanceCheck(S.draft);
4300
4383
  // 【P1.2.50】标记实际生成了凭证的规则为「已生成」,持久化到 localStorage
4301
4384
  (res.rows||[]).forEach(r=>{ const rid=r['规则编号']; if(rid) S.genRules.add(String(rid)); });
4385
+ // 【P1.2.60/61】记录每条规则本次生成的凭证号范围,回显到规则卡片上;保留未选中规则的旧号
4386
+ S.ruleVnos=Object.assign({}, S.ruleVnos||{}, res.ruleVnos||{});
4302
4387
  DB.put('misc','draft',S.draft); DB.put('misc','missing',S.missing);
4303
4388
  saveCfg();
4304
4389
  renderRuleChecks();
@@ -5961,8 +6046,9 @@ window.UI={
5961
6046
  toggleRuleBody:i=>{const b=$('rb'+i);if(b)b.classList.toggle('on');},
5962
6047
  toggleRuleSel:cb=>{cb.checked?S.selRules.add(cb.dataset.rid):S.selRules['delete'](cb.dataset.rid);},
5963
6048
  selAllRules:v=>{S.selRules.clear();if(v)S.rules.forEach(r=>S.selRules.add(r.id));renderRuleChecks();},
6049
+ renderRuleChecks:renderRuleChecks,
5964
6050
  // 【P1.2.50】清空「已生成」绿色标记(规则删除时也会同步清理)
5965
- clearGenerated:()=>{ if(!S.genRules.size){ toast('没有已生成标记可清空'); return; } if(!confirm('确定清空全部「已生成」绿色标记?已生成的凭证草稿不会受影响。'))return; S.genRules.clear(); saveCfg(); renderRuleChecks(); toast('已清空生成标记'); },
6051
+ clearGenerated:()=>{ if(!S.genRules.size){ toast('没有已生成标记可清空'); return; } if(!confirm('确定清空全部「已生成」绿色标记?已生成的凭证草稿不会受影响。'))return; S.genRules.clear(); S.ruleVnos={}; saveCfg(); renderRuleChecks(); toast('已清空生成标记'); },
5966
6052
  // 【P1.2.35】全局排除弹窗:按业务源分组的总开关(在外面,不嵌在每条规则里)
5967
6053
  openGlobalExcludes:()=>{
5968
6054
  const ge = S.globalExcludes || (S.globalExcludes = {});
package/dist/version.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
- "ver": "P1.2.59",
3
- "npm": "1.2.59",
4
- "url": "https://cdn.jsdelivr.net/npm/wwt-certificate-tool@1.2.59/dist/app.html",
5
- "url_unpkg": "https://unpkg.com/wwt-certificate-tool@1.2.59/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.59.html",
7
- "note": "① 内置默认基础表/规则/映射:新安装或分享打开即自带当前这套标准配置。② 生成凭证后实际生成了凭证的规则自动变绿并显示「已生成」徽章,状态持久化。③ 新增「清空已生成」按钮。④ 凭证起始号放到「④ 生成凭证」页生成栏。⑤ 对碰检测跳过未结算单据,缺失清单增加「单号」与「付款方式/结算方式」列。⑥ 凭证审核表「核算维度」列移到「科目名称」后。⑦ 分录级拆分字段:每条分录可单独设置拆分字段,实现一张凭证内借方按单号拆、贷方汇总等混合拆分。⑧ 【P1.2.55】顶部工具栏增加「清空业务数据」按钮,只清本月业务数据表,保留基础数据、规则、映射和配置。⑨ 【P1.2.56】生成/审核自动跳过 Excel 底部的「合计/小计/总计」行,避免汇总金额被重复累加(如 人进价 145万 翻倍的翻倍问题);合计行在编辑器内仍可见、只是不参与凭证生成。⑩ 【P1.2.57】修复合计行识别:扫描行内所有字段,避免「合计」写在摘要/备注/行类型等非标识列时仍被误当作数据行累加。⑪ 【P1.2.58】合计行兜底识别:若某行「单位名称/供应商名称/单号等标识列全空 + 有非零金额」(即合计行无合计字样),也判定为合计行跳过,彻底杜绝金额翻倍。⑫ 【P1.2.59】修复全部汇总模式下摘要把全组供应商拼接的问题:{供应商名称}/{单位名称} 各自读取对应字段,且摘要上下文按最终分录小组构建,同一凭证内多个供应商不再互相串到摘要里。"
2
+ "ver": "P1.2.61",
3
+ "npm": "1.2.61",
4
+ "url": "https://cdn.jsdelivr.net/npm/wwt-certificate-tool@1.2.61/dist/app.html",
5
+ "url_unpkg": "https://unpkg.com/wwt-certificate-tool@1.2.61/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.61.html",
7
+ "note": "① 内置默认基础表/规则/映射:新安装或分享打开即自带当前这套标准配置。② 生成凭证后实际生成了凭证的规则自动变绿并显示「已生成」徽章,状态持久化。③ 新增「清空已生成」按钮。④ 凭证起始号放到「④ 生成凭证」页生成栏。⑤ 对碰检测跳过未结算单据,缺失清单增加「单号」与「付款方式/结算方式」列。⑥ 凭证审核表「核算维度」列移到「科目名称」后。⑦ 分录级拆分字段:每条分录可单独设置拆分字段,实现一张凭证内借方按单号拆、贷方汇总等混合拆分。⑧ 【P1.2.55】顶部工具栏增加「清空业务数据」按钮,只清本月业务数据表,保留基础数据、规则、映射和配置。⑨ 【P1.2.56】生成/审核自动跳过 Excel 底部的「合计/小计/总计」行,避免汇总金额被重复累加(如 人进价 145万 翻倍的翻倍问题);合计行在编辑器内仍可见、只是不参与凭证生成。⑩ 【P1.2.57】修复合计行识别:扫描行内所有字段,避免「合计」写在摘要/备注/行类型等非标识列时仍被误当作数据行累加。⑪ 【P1.2.58】合计行兜底识别:若某行「单位名称/供应商名称/单号等标识列全空 + 有非零金额」(即合计行无合计字样),也判定为合计行跳过,彻底杜绝金额翻倍。⑫ 【P1.2.59】修复全部汇总模式下摘要把全组供应商拼接的问题:{供应商名称}/{单位名称} 各自读取对应字段,且摘要上下文按最终分录小组构建,同一凭证内多个供应商不再互相串到摘要里。⑬ 【P1.2.60】多规则全选生成时按优先级排序:先「入库未确认/出库未确认/调拨未确认/在修车/已完工未结算」,再按付款方式/支付方式「对冲F/对账S/对账F/还件F/入库F/冲转S/冲转F/挂账/冲预收/冲预付/现金S/银行S/现金F/银行F」凑近凭证号;规则卡片显示本次生成的凭证号范围,方便核对。⑭ 【P1.2.61】重复生成时保留已有规则凭证号:第二次点生成若混选新旧规则,旧规则保持原凭证号不变,新规则从当前最大已用凭证号+1 往后延续,避免 150/151/150 这种号被覆盖的情况。"
8
8
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wwt-certificate-tool",
3
- "version": "1.2.59",
3
+ "version": "1.2.61",
4
4
  "description": "WWT 金蝶凭证工具 · 纯前端版(离线单文件 HTML,双击即用)",
5
5
  "keywords": [
6
6
  "WWT",