wwt-ar-summary 1.0.1 → 1.1.0
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/README.md +1 -1
- package/ar-summary.html +118 -46
- package/package.json +1 -1
- package/version.json +1 -1
package/README.md
CHANGED
package/ar-summary.html
CHANGED
|
@@ -112,7 +112,7 @@ aside#cfgPanel textarea {
|
|
|
112
112
|
|
|
113
113
|
<header>
|
|
114
114
|
<h1>应收帐款汇总表</h1>
|
|
115
|
-
<span class="sub" id="verLine">v1.0
|
|
115
|
+
<span class="sub" id="verLine">v1.1.0</span>
|
|
116
116
|
<span class="grow"></span>
|
|
117
117
|
<span id="updateMsg"></span>
|
|
118
118
|
<button class="btn" id="btnCheck">检查更新</button>
|
|
@@ -132,8 +132,9 @@ aside#cfgPanel textarea {
|
|
|
132
132
|
<div class="card" id="filePanel" style="display:none">
|
|
133
133
|
<h2>2 · 确认识别结果</h2>
|
|
134
134
|
<table class="files">
|
|
135
|
-
<thead><tr><th>文件</th><th style="width:
|
|
136
|
-
<th style="width:
|
|
135
|
+
<thead><tr><th>文件</th><th style="width:130px">工作表</th><th style="width:180px">识别为</th>
|
|
136
|
+
<th style="width:120px">依据</th>
|
|
137
|
+
<th style="width:70px">行数</th><th style="width:140px">金额合计</th><th style="width:60px"></th></tr></thead>
|
|
137
138
|
<tbody id="fileBody"></tbody>
|
|
138
139
|
</table>
|
|
139
140
|
<div style="margin-top:14px; display:flex; gap:10px; align-items:center">
|
|
@@ -192,7 +193,7 @@ aside#cfgPanel textarea {
|
|
|
192
193
|
|
|
193
194
|
<script>
|
|
194
195
|
window.APP_META = {
|
|
195
|
-
appVersion: '1.0
|
|
196
|
+
appVersion: '1.1.0',
|
|
196
197
|
appPkg: 'wwt-ar-summary',
|
|
197
198
|
appUpdateFile: 'ar-summary.html',
|
|
198
199
|
updateSources: []
|
|
@@ -285,15 +286,18 @@ https://github.com/nodeca/pako/blob/main/LICENSE
|
|
|
285
286
|
'广州维修基地', '加工部', '阀体车间', '研发中心'],
|
|
286
287
|
// 类型列(出库单类 / 收费类型)含这些词 → 内部
|
|
287
288
|
typeKeywords: ['复检', 'WWT再制造', '调库销售单'],
|
|
289
|
+
// 表名/sheet 名含这些词 → 不是明细源表(汇总、透视、库存、入库、调拨都不参与应收)
|
|
290
|
+
skipKeywords: ['汇总', '统计', '透视', '库存', '入库', '调拨', '三项费用', '说明'],
|
|
288
291
|
// 五张源表:文件名关键词 + 表头指纹 + 金额列 + 单号列 + 归属列
|
|
289
292
|
tables: [
|
|
290
|
-
{ key: 'xs', name: '销售未结算', slot: 2, fileKw: ['出库未确认', '
|
|
293
|
+
{ key: 'xs', name: '销售未结算', slot: 2, fileKw: ['出库未确认', '销售未确认', '销售未结算'],
|
|
291
294
|
must: ['出库单类'], money: '实收金额', doc: '出库单号', typeCol: '出库单类' },
|
|
292
|
-
|
|
295
|
+
// 关键词必须带"销售"前缀:'已挂账未清账' 这种通用词会把"维修已挂账未清账"也抢过来
|
|
296
|
+
{ key: 'xz', name: '销售已挂账未清账', slot: 3, fileKw: ['销售挂账', '销售已挂账', '销售未清账'],
|
|
293
297
|
must: ['出库单类'], money: '实收金额', doc: '出库单号', typeCol: '出库单类' },
|
|
294
|
-
{ key: 'wx', name: '维修已挂账未清账', slot: 3, fileKw: ['维修挂账', '维修已挂账'],
|
|
298
|
+
{ key: 'wx', name: '维修已挂账未清账', slot: 3, fileKw: ['维修挂账', '维修已挂账', '维修未清账'],
|
|
295
299
|
must: ['客户实收额'], money: '客户实收额', doc: '工单号', typeCol: '收费类型' },
|
|
296
|
-
{ key: 'wg', name: '完工未结算', slot: 0, fileKw: ['完工未结算', '
|
|
300
|
+
{ key: 'wg', name: '完工未结算', slot: 0, fileKw: ['完工未结算', '已完工'],
|
|
297
301
|
must: ['A结算金额'], money: 'A结算金额', doc: '工单号', typeCol: '收费类型' },
|
|
298
302
|
{ key: 'zx', name: '在修未结算', slot: 1, fileKw: ['在修'],
|
|
299
303
|
must: ['A结算金额'], money: 'A结算金额', doc: '工单号', typeCol: '收费类型' }
|
|
@@ -344,28 +348,53 @@ https://github.com/nodeca/pako/blob/main/LICENSE
|
|
|
344
348
|
* 返回 { table: 命中的表定义, how: '文件名'|'表头指纹', candidates: [...] }
|
|
345
349
|
* 文件名优先(销售两张 / 完工与在修 的表头相同,只能靠文件名区分)
|
|
346
350
|
*/
|
|
347
|
-
function detectTable(fileName, header, cfg) {
|
|
351
|
+
function detectTable(fileName, header, cfg, sheetName) {
|
|
348
352
|
cfg = cfg || DEFAULT_CFG;
|
|
349
353
|
var fn = norm(fileName).toLowerCase();
|
|
354
|
+
var sh = norm(sheetName || '').toLowerCase(); // 跳过规则只看 sheet 名,不看文件名
|
|
355
|
+
|
|
356
|
+
// 汇总/透视/库存/入库/调拨 这类表直接排除——它们的表头也含"单位名称",不排除会误判
|
|
357
|
+
var skipped = sh ? (cfg.skipKeywords || []).filter(function (k) { return sh.indexOf(norm(k).toLowerCase()) >= 0; }) : [];
|
|
358
|
+
if (skipped.length) {
|
|
359
|
+
return { table: null, how: '跳过(' + skipped.join('/') + ')', skipped: true, candidates: [] };
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/** 在一段文本里找该表命中的最长关键词 */
|
|
363
|
+
function kwLen(t, text) {
|
|
364
|
+
var best = '';
|
|
365
|
+
t.fileKw.forEach(function (k) {
|
|
366
|
+
var kn = norm(k).toLowerCase();
|
|
367
|
+
if (kn && text.indexOf(kn) >= 0 && kn.length > best.length) best = kn;
|
|
368
|
+
});
|
|
369
|
+
return best.length;
|
|
370
|
+
}
|
|
371
|
+
|
|
350
372
|
var hits = [];
|
|
351
373
|
cfg.tables.forEach(function (t) {
|
|
352
|
-
var byName = t.fileKw.some(function (k) { return fn.indexOf(norm(k).toLowerCase()) >= 0; });
|
|
353
374
|
var byHead = t.must.every(function (m) { return findCol(header, [m]) >= 0; });
|
|
354
|
-
|
|
355
|
-
|
|
375
|
+
// 工作表名优先("在修""销售未确认"这种表名最可靠),文件名兜底
|
|
376
|
+
var lenSh = sh ? kwLen(t, sh) : 0;
|
|
377
|
+
var lenFn = kwLen(t, fn);
|
|
378
|
+
if (lenSh) hits.push({ t: t, how: byHead ? '工作表名+表头' : '工作表名', len: lenSh, pri: 2 });
|
|
379
|
+
else if (lenFn) hits.push({ t: t, how: byHead ? '文件名+表头' : '文件名', len: lenFn, pri: 1 });
|
|
356
380
|
});
|
|
357
|
-
if (hits.length === 1) return { table: hits[0].t, how: hits[0].how, candidates: hits.map(function (h) { return h.t.name; }) };
|
|
358
381
|
|
|
359
|
-
//
|
|
382
|
+
// 命中多张时:工作表名命中优先,其次匹配关键词更长者(更具体者优先)
|
|
383
|
+
hits.sort(function (a, b) { return (b.pri - a.pri) || (b.len - a.len); });
|
|
384
|
+
if (hits.length) {
|
|
385
|
+
return {
|
|
386
|
+
table: hits[0].t, how: hits[0].how,
|
|
387
|
+
candidates: hits.map(function (h) { return h.t.name; })
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// 文件名没命中:仅当表头指纹唯一时才自动认定,否则交给用户指定
|
|
360
392
|
var headOnly = cfg.tables.filter(function (t) {
|
|
361
393
|
return t.must.every(function (m) { return findCol(header, [m]) >= 0; });
|
|
362
394
|
});
|
|
363
|
-
if (
|
|
364
|
-
|
|
365
|
-
hits.sort(function (a, b) { return b.how.length - a.how.length; });
|
|
366
|
-
return { table: hits[0].t, how: hits[0].how, candidates: hits.map(function (h) { return h.t.name; }) };
|
|
395
|
+
if (headOnly.length === 1) {
|
|
396
|
+
return { table: headOnly[0], how: '表头指纹', candidates: [headOnly[0].name] };
|
|
367
397
|
}
|
|
368
|
-
if (headOnly.length === 1) return { table: headOnly[0], how: '表头指纹', candidates: [headOnly[0].name] };
|
|
369
398
|
return { table: null, how: '未识别', candidates: headOnly.map(function (t) { return t.name; }) };
|
|
370
399
|
}
|
|
371
400
|
|
|
@@ -507,6 +536,7 @@ https://github.com/nodeca/pako/blob/main/LICENSE
|
|
|
507
536
|
r2: r2
|
|
508
537
|
};
|
|
509
538
|
});
|
|
539
|
+
|
|
510
540
|
</script>
|
|
511
541
|
<script>/* ============================================================================
|
|
512
542
|
* 自更新:启动查最新版 → 下载校验 → 内存原地替换(不写磁盘)
|
|
@@ -837,43 +867,80 @@ https://github.com/nodeca/pako/blob/main/LICENSE
|
|
|
837
867
|
return new Promise(function (resolve) {
|
|
838
868
|
var fr = new FileReader();
|
|
839
869
|
fr.onload = function () {
|
|
870
|
+
var items = [];
|
|
840
871
|
try {
|
|
841
872
|
var wb = XLSX.read(new Uint8Array(fr.result), { type: 'array', cellDates: true });
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
873
|
+
log(' ' + f.name + ':共 ' + wb.SheetNames.length + ' 个工作表,逐个识别…');
|
|
874
|
+
|
|
875
|
+
wb.SheetNames.forEach(function (sn) {
|
|
876
|
+
var aoa;
|
|
877
|
+
try {
|
|
878
|
+
aoa = XLSX.utils.sheet_to_json(wb.Sheets[sn], { header: 1, raw: true, defval: null });
|
|
879
|
+
} catch (e) { return; }
|
|
880
|
+
if (!aoa || !aoa.length) return;
|
|
881
|
+
|
|
882
|
+
var hi = findHeaderRow(aoa);
|
|
883
|
+
var header = (aoa[hi] || []).map(function (c) { return c == null ? '' : String(c).trim(); });
|
|
884
|
+
var rows = aoa.slice(hi + 1);
|
|
885
|
+
// 只有含"单位名称"的才是明细表;汇总/透视那种没有这列
|
|
886
|
+
if (Core.findCol(header, ['单位名称']) < 0) {
|
|
887
|
+
if (rows.length > 1) log(' ' + sn + ':非明细表,跳过', 'dim');
|
|
888
|
+
return;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
var det = Core.detectTable(f.name, header, STATE.cfg, sn);
|
|
892
|
+
if (det.skipped) { log(' ' + sn + ':' + det.how, 'dim'); return; }
|
|
893
|
+
|
|
894
|
+
var moneyCol = det.table ? Core.findCol(header, [det.table.money]) : -1;
|
|
895
|
+
var docCol = det.table ? Core.findCol(header, [det.table.doc]) : -1;
|
|
896
|
+
var sum = 0, cnt = 0;
|
|
897
|
+
if (moneyCol >= 0) {
|
|
898
|
+
rows.forEach(function (r) {
|
|
899
|
+
var d = docCol >= 0 ? Core.norm(r[docCol]) : '';
|
|
900
|
+
if (docCol >= 0 && (!d || d.indexOf('合计') >= 0 || d.indexOf('总计') >= 0)) return;
|
|
901
|
+
if (r[moneyCol] != null && r[moneyCol] !== '') { sum += Core.toNum(r[moneyCol]); cnt++; }
|
|
902
|
+
});
|
|
903
|
+
}
|
|
904
|
+
items.push({
|
|
905
|
+
name: f.name, size: f.size, sheet: sn, header: header, rows: rows,
|
|
906
|
+
table: det.table, how: det.how, moneySum: sum, count: cnt
|
|
856
907
|
});
|
|
908
|
+
log(' ' + sn + ' → ' + (det.table ? det.table.name + '(' + det.how + ',' + cnt + ' 行)'
|
|
909
|
+
: '未识别,可在列表里指定或忽略'), det.table ? 'ok' : 'warn');
|
|
910
|
+
});
|
|
911
|
+
|
|
912
|
+
if (!items.length) {
|
|
913
|
+
log(' ' + f.name + ':没有找到可汇总的明细表', 'warn');
|
|
914
|
+
items.push({ name: f.name, size: f.size, sheet: '(无)', table: null, count: 0,
|
|
915
|
+
moneySum: 0, error: '未找到明细表' });
|
|
857
916
|
}
|
|
858
|
-
resolve({ name: f.name, size: f.size, sheet: sn, header: header, rows: rows,
|
|
859
|
-
table: det.table, how: det.how, moneySum: sum, count: cnt });
|
|
860
|
-
log(' → ' + (det.table ? det.table.name + '(' + det.how + ',' + cnt + ' 行)'
|
|
861
|
-
: '未能识别,请在列表里手动指定'), det.table ? 'ok' : 'warn');
|
|
862
917
|
} catch (e) {
|
|
863
918
|
log(' 解析失败:' + (e && e.message || e), 'bad');
|
|
864
|
-
|
|
919
|
+
items.push({ name: f.name, size: f.size, sheet: '(整份)', error: String(e && e.message || e), table: null });
|
|
865
920
|
}
|
|
921
|
+
resolve(items);
|
|
866
922
|
};
|
|
867
|
-
fr.onerror = function () { resolve({ name: f.name, error: '读取失败', table: null }); };
|
|
923
|
+
fr.onerror = function () { resolve([{ name: f.name, sheet: '(整份)', error: '读取失败', table: null }]); };
|
|
868
924
|
fr.readAsArrayBuffer(f);
|
|
869
925
|
});
|
|
870
926
|
});
|
|
871
927
|
|
|
872
|
-
Promise.all(jobs).then(function (
|
|
873
|
-
list.forEach(function (
|
|
928
|
+
Promise.all(jobs).then(function (lists) {
|
|
929
|
+
lists.forEach(function (list) { list.forEach(function (it) { STATE.files.push(it); }); });
|
|
874
930
|
STATE.busy = false;
|
|
931
|
+
|
|
932
|
+
// 从文件名推断账期(如 202610统计-DPS6.xlsx → 202610)
|
|
933
|
+
var tagEl = $('periodTag');
|
|
934
|
+
if (!tagEl.value) {
|
|
935
|
+
for (var i = 0; i < arr.length; i++) {
|
|
936
|
+
var m = /(20\d{4})/.exec(arr[i].name);
|
|
937
|
+
if (m) { tagEl.value = m[1]; break; }
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
|
|
875
941
|
renderFiles();
|
|
876
|
-
|
|
942
|
+
var okCount = STATE.files.filter(function (f) { return f.table && !f.error; }).length;
|
|
943
|
+
log('共 ' + STATE.files.length + ' 个明细表就绪,其中 ' + okCount + ' 个已识别', 'ok');
|
|
877
944
|
});
|
|
878
945
|
}
|
|
879
946
|
|
|
@@ -889,9 +956,10 @@ https://github.com/nodeca/pako/blob/main/LICENSE
|
|
|
889
956
|
}).join('');
|
|
890
957
|
tr.innerHTML =
|
|
891
958
|
'<td>' + esc(f.name) + '</td>' +
|
|
892
|
-
'<td
|
|
893
|
-
'<td
|
|
894
|
-
|
|
959
|
+
'<td>' + esc(f.sheet || '') + '</td>' +
|
|
960
|
+
'<td><select data-i="' + i + '"><option value="">(忽略,不计入)</option>' + op + '</select></td>' +
|
|
961
|
+
'<td>' + (f.error ? '<span class="tag bad">' + esc(f.error) + '</span>'
|
|
962
|
+
: (f.table ? '<span class="tag ok">' + esc(f.how) + '</span>' : '<span class="tag warn">待指定</span>')) + '</td>' +
|
|
895
963
|
'<td class="n">' + (f.count != null ? f.count : '') + '</td>' +
|
|
896
964
|
'<td class="n">' + (f.moneySum != null ? fmt(f.moneySum) : '') + '</td>' +
|
|
897
965
|
'<td><button class="btn sm" data-del="' + i + '">移除</button></td>';
|
|
@@ -925,7 +993,11 @@ https://github.com/nodeca/pako/blob/main/LICENSE
|
|
|
925
993
|
function build() {
|
|
926
994
|
var usable = STATE.files.filter(function (f) { return f.table && !f.error; });
|
|
927
995
|
if (!usable.length) { log('没有可汇总的文件', 'bad'); return; }
|
|
928
|
-
|
|
996
|
+
var kinds = {};
|
|
997
|
+
usable.forEach(function (f) { kinds[f.table.key] = (kinds[f.table.key] || 0) + 1; });
|
|
998
|
+
if (Object.keys(kinds).length < 5) {
|
|
999
|
+
log('提示:只识别到 ' + Object.keys(kinds).length + ' 类明细表(完整口径是 5 类:销售未结算 / 销售挂账 / 维修挂账 / 完工未结算 / 在修)', 'warn');
|
|
1000
|
+
}
|
|
929
1001
|
|
|
930
1002
|
STATE.busy = true;
|
|
931
1003
|
var res = Core.summarize(usable.map(function (f) {
|
package/package.json
CHANGED