weaver-work-cli 0.1.28 → 0.1.31
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/chunks/archive-U766FTFH.js +2537 -0
- package/dist/chunks/archive-oa-DMWEKX3L.js +1317 -0
- package/dist/chunks/asset-TCR7VDEV.js +2109 -0
- package/dist/chunks/attachment-content-AQNX5N3S.js +23 -0
- package/dist/chunks/blog-EVZWGDVM.js +2157 -0
- package/dist/chunks/caizhilian-JOWS6SL5.js +10087 -0
- package/dist/chunks/calendar-5WNCV66Q.js +1195 -0
- package/dist/chunks/chunk-2DUV44UX.js +15606 -0
- package/dist/chunks/chunk-324YPTC2.js +1607 -0
- package/dist/chunks/chunk-65QMVWHS.js +231 -0
- package/dist/chunks/chunk-6V7ZLF34.js +387 -0
- package/dist/chunks/chunk-AC7KJWC5.js +358 -0
- package/dist/chunks/chunk-AJ62BGAW.js +29 -0
- package/dist/chunks/chunk-BUSC6UAE.js +43 -0
- package/dist/chunks/chunk-CVZ2JOSW.js +62 -0
- package/dist/chunks/chunk-D4LTJSJD.js +477 -0
- package/dist/chunks/chunk-EG6ZU444.js +654 -0
- package/dist/chunks/chunk-EZALDOQH.js +383 -0
- package/dist/chunks/chunk-FMPJJYYI.js +106 -0
- package/dist/chunks/chunk-G2JSIUWF.js +769 -0
- package/dist/chunks/chunk-GCG2NMWR.js +703 -0
- package/dist/chunks/chunk-GPMFD4RD.js +75 -0
- package/dist/chunks/chunk-H3WDBYJW.js +508 -0
- package/dist/chunks/chunk-H6XSTSLM.js +2357 -0
- package/dist/chunks/chunk-J7RJTZSV.js +5556 -0
- package/dist/chunks/chunk-LUSPKZKY.js +53 -0
- package/dist/chunks/chunk-NIAJ5ZCG.js +73 -0
- package/dist/chunks/chunk-NOODWMCL.js +584 -0
- package/dist/chunks/chunk-OOUTBLZ4.js +51 -0
- package/dist/chunks/chunk-P26ZWSKU.js +73 -0
- package/dist/chunks/chunk-QWY6PGL6.js +3472 -0
- package/dist/chunks/chunk-R2IJAK7T.js +464 -0
- package/dist/chunks/chunk-R7O5PUWO.js +205 -0
- package/dist/chunks/chunk-RCGYCOMG.js +6962 -0
- package/dist/chunks/chunk-VZTXRG6X.js +403 -0
- package/dist/chunks/chunk-WHD4XMRP.js +161 -0
- package/dist/chunks/chunk-YLCYX7LE.js +371 -0
- package/dist/chunks/chunk-ZEDWSH32.js +68 -0
- package/dist/chunks/continuation-2CKSBLA6.js +11 -0
- package/dist/chunks/continuation-HZIZRZBX.js +13 -0
- package/dist/chunks/continuation-NLECEJPX.js +11 -0
- package/dist/chunks/ebuilder-form-NJ4YK6CC.js +2276 -0
- package/dist/chunks/ehr-MMYN36VA.js +5241 -0
- package/dist/chunks/employee-SAK2QYXX.js +11 -0
- package/dist/chunks/esb-HIWHOU7Q.js +141 -0
- package/dist/chunks/fna-KBACQTA5.js +1444 -0
- package/dist/chunks/host-3OHS5QUX.js +21 -0
- package/dist/chunks/host-IG6KEH5Q.js +19 -0
- package/dist/chunks/host-KQL5N4HO.js +23 -0
- package/dist/chunks/host-LLSZQCWR.js +17 -0
- package/dist/chunks/hrm-LYEC67ZQ.js +244 -0
- package/dist/chunks/im-RN2UKAEU.js +252 -0
- package/dist/chunks/invoice-EEPHLAF6.js +5179 -0
- package/dist/chunks/jiuchuanhui-WVVZYSJE.js +1940 -0
- package/dist/chunks/lazy-dispatch-MDOFFL2S.js +28 -0
- package/dist/chunks/mail-LNQLTF6L.js +2695 -0
- package/dist/chunks/meeting-XSD3BDVQ.js +504 -0
- package/dist/chunks/msg-parse-4GSQ2WTO.js +41 -0
- package/dist/chunks/okr-TMWTIU2K.js +2482 -0
- package/dist/chunks/plan-A2FSFA7T.js +2025 -0
- package/dist/chunks/project-T26QLBQF.js +1361 -0
- package/dist/chunks/registry-2VLUAESA.js +14 -0
- package/dist/chunks/registry-JCFEYXOE.js +18 -0
- package/dist/chunks/registry-NYPYMZXG.js +16 -0
- package/dist/chunks/registry-Q7OQJ3SU.js +19 -0
- package/dist/chunks/registry-ZJMJVWFA.js +19 -0
- package/dist/chunks/room-recommend-UR4ZDZS6.js +20 -0
- package/dist/chunks/schedule-5L2PM4M6.js +24 -0
- package/dist/chunks/state-Y2XBP6YE.js +15 -0
- package/dist/chunks/workflow-TR6PG2SL.js +245 -0
- package/dist/cmd/argv.js +45 -0
- package/dist/cmd/docs/index.js +3 -3
- package/dist/cmd/lazy-dispatch.js +20 -0
- package/dist/cmd/root.js +2 -4
- package/dist/cmd/setup/index.js +2 -2
- package/dist/cmd/skills/index.js +2 -2
- package/dist/core/package-paths.js +47 -0
- package/dist/index.js +163 -7
- package/dist/internal/e10/auth/xiaoe.js +3 -3
- package/dist/internal/e10/request-runtime.js +18 -62
- package/dist/internal/skills/detector.js +2 -2
- package/dist/shortcuts/contract/data/performance-catalog.js +1306 -0
- package/dist/shortcuts/contract/host.js +91 -35
- package/dist/shortcuts/contract/index.js +213 -0
- package/dist/shortcuts/contract/manifest.js +928 -0
- package/dist/shortcuts/contract/ooxml/docx.js +295 -0
- package/dist/shortcuts/contract/ooxml/xlsx.js +221 -0
- package/dist/shortcuts/contract/ooxml/zip.js +158 -0
- package/dist/shortcuts/contract/operations/assistant.js +1156 -0
- package/dist/shortcuts/contract/operations/clause-library.js +522 -0
- package/dist/shortcuts/contract/operations/compare.js +193 -0
- package/dist/shortcuts/contract/operations/credit-page.js +34 -0
- package/dist/shortcuts/contract/operations/flow.js +98 -0
- package/dist/shortcuts/contract/operations/page.js +66 -0
- package/dist/shortcuts/contract/operations/performance.js +733 -0
- package/dist/shortcuts/contract/operations/quote.js +557 -0
- package/dist/shortcuts/contract/operations/recommend.js +78 -0
- package/dist/shortcuts/contract/operations/registry.js +18 -0
- package/dist/shortcuts/contract/operations/review.js +83 -0
- package/dist/shortcuts/contract/operations/shared.js +37 -0
- package/dist/shortcuts/contract/operations/upload.js +108 -0
- package/dist/shortcuts/contract/operations/write.js +3 -2
- package/dist/shortcuts/index.js +140 -40
- package/dist/shortcuts/invoice/host.js +2 -1
- package/dist/shortcuts/invoice/manifest.js +36 -12
- package/dist/shortcuts/invoice/operations/issuing-qys.js +132 -36
- package/dist/shortcuts/invoice/operations/reim-auto.js +9 -0
- package/dist/shortcuts/invoice/operations/reim.js +81 -5
- package/dist/shortcuts/invoice/operations/shared.js +12 -5
- package/dist/shortcuts/meeting/host.js +45 -3
- package/dist/shortcuts/meeting/invitation/docx.js +442 -0
- package/dist/shortcuts/meeting/operations/attachment-content.js +418 -0
- package/dist/shortcuts/meeting/operations/attachment.js +442 -0
- package/dist/shortcuts/meeting/operations/eb/cache.js +57 -0
- package/dist/shortcuts/meeting/operations/eb/catalog.js +343 -28
- package/dist/shortcuts/meeting/operations/eb/conflict.js +372 -11
- package/dist/shortcuts/meeting/operations/eb/create-flow.js +710 -0
- package/dist/shortcuts/meeting/operations/eb/decision.js +47 -1
- package/dist/shortcuts/meeting/operations/eb/factory.js +129 -11
- package/dist/shortcuts/meeting/operations/eb/index.js +122 -2
- package/dist/shortcuts/meeting/operations/eb/room-recommend.js +378 -0
- package/dist/shortcuts/meeting/operations/eb/room-summary.js +84 -0
- package/dist/shortcuts/meeting/operations/eb/schedule.js +450 -0
- package/dist/shortcuts/meeting/operations/from-group-chat.js +576 -0
- package/dist/shortcuts/meeting/operations/invitation.js +458 -0
- package/dist/shortcuts/meeting/operations/minute-content.js +266 -0
- package/dist/shortcuts/meeting/operations/pipeline.js +489 -0
- package/dist/shortcuts/meeting/operations/qrcode.js +131 -0
- package/dist/shortcuts/meeting/operations/receipts.js +1 -1
- package/dist/shortcuts/meeting/operations/registry.js +18 -0
- package/dist/shortcuts/meeting/operations/summary.js +276 -0
- package/dist/shortcuts/meeting/operations/task-notify.js +325 -0
- package/dist/shortcuts/meeting/operations.js +42 -1
- package/dist/shortcuts/meeting/qr/encoder.js +427 -0
- package/dist/shortcuts/meeting/qr/render.js +103 -0
- package/dist/shortcuts/workflow/index.js +3 -0
- package/dist/shortcuts/workflow/operations/form.js +17 -5
- package/dist/shortcuts/workflow/operations/read.js +13 -6
- package/dist/shortcuts/workflow/operations/registry.js +2 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/businessRules.js +331 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/detailFieldRules.js +133 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/summaryCache.js +326 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoDetailCache.js +113 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoDetailFetcher.js +259 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoListRows.js +64 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoOutputPlanner.js +28 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.aggregate.js +70 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.collect.js +267 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.core.js +392 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.detail.js +165 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.group.js +448 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.groupSummary.js +348 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.js +222 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.pipeline.js +66 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.smartGroup.js +502 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.summary.js +540 -0
- package/docs/_catalog.md +1 -0
- package/docs/contract.md +39 -4
- package/docs/invoice.md +7 -7
- package/docs/todo-smart-convert/README.md +171 -0
- package/docs/todo-smart-convert/data-contract.md +236 -0
- package/docs/todo-smart-convert/mock/ai-todo-query-target.json +209 -0
- package/docs/todo-smart-convert/subgroup-rules.md +245 -0
- package/package.json +3 -2
- package/scripts/bundle.mjs +63 -0
- package/scripts/package-skill.mjs +1 -1
- package/skill-template/business-info.json +25 -4
- package/skill-template/domains/jinchengda.md +16 -3
- package/skill-template/domains/yepiaotong.md +3 -3
- package/skills/weaver-e10-jinchengda/SKILL.md +111 -31
- package/skills/weaver-e10-jinchengda/references/contract-agent-entry.md +7 -13
- package/skills/weaver-e10-jinchengda/references/contract-ai-assistant.md +150 -0
- package/skills/weaver-e10-jinchengda/references/contract-ai-review.md +87 -0
- package/skills/weaver-e10-jinchengda/references/contract-clause-library-crud.md +111 -0
- package/skills/weaver-e10-jinchengda/references/contract-clause-recommender.md +0 -15
- package/skills/weaver-e10-jinchengda/references/contract-clause-reference-save.md +0 -13
- package/skills/weaver-e10-jinchengda/references/contract-credit-page.md +50 -0
- package/skills/weaver-e10-jinchengda/references/contract-disabled-capabilities.md +4 -2
- package/skills/weaver-e10-jinchengda/references/contract-doc-compare.md +74 -0
- package/skills/weaver-e10-jinchengda/references/contract-flow-launch.md +84 -0
- package/skills/weaver-e10-jinchengda/references/contract-performance-query.md +29 -0
- package/skills/weaver-e10-jinchengda/references/contract-quote-to-contract.md +28 -0
- package/skills/weaver-e10-jinchengda/references/contract-request-status.md +2 -21
- package/skills/weaver-e10-jinchengda/references/contract-request-write.md +0 -24
- package/skills/weaver-e10-jinchengda/references/contract-signing-subjects.md +0 -15
- package/skills/weaver-e10-jinchengda/references/contract-subject-create.md +0 -17
- package/skills/weaver-e10-jinchengda/references/contract-template-recommend.md +70 -0
- package/skills/weaver-e10-jinchengda/references/contract-template-selector.md +0 -15
- package/skills/weaver-e10-jinchengda/references/contract-workflow-discovery.md +0 -15
- package/skills/weaver-e10-jinchengda/references/source-manifest.json +1168 -152
- package/skills/weaver-e10-meeting/SKILL.md +493 -17
- package/skills/weaver-e10-meeting/references/meeting-change.md +4 -4
- package/skills/weaver-e10-meeting/references/meeting-create.md +6 -6
- package/skills/weaver-e10-meeting/references/meeting-eb.md +503 -27
- package/skills/weaver-e10-meeting/references/meeting-invitation.md +113 -0
- package/skills/weaver-e10-meeting/references/meeting-lifecycle.md +6 -6
- package/skills/weaver-e10-meeting/references/meeting-minute-file.md +425 -0
- package/skills/weaver-e10-meeting/references/meeting-queries.md +4 -4
- package/skills/weaver-e10-meeting/references/meeting-receipts-signs.md +15 -15
- package/skills/weaver-e10-meeting/references/meeting-verification.md +19 -11
- package/skills/weaver-e10-meeting.zip +0 -0
- package/skills/weaver-e10-workflow/SKILL.md +23 -1
- package/skills/weaver-e10-workflow/references/todo-smart-convert.md +282 -0
- package/skills/weaver-e10-workflow/scripts/todo-summary.cjs +695 -0
- package/skills/weaver-e10-yepiaotong/SKILL.md +15 -11
- package/skills/weaver-e10-yepiaotong/references/invoice-enterprise-list.md +1 -1
- package/skills/weaver-e10-yepiaotong/references/invoice-issuing-examples.md +10 -7
- package/skills/weaver-e10-yepiaotong/references/invoice-issuing-form-fill-rules.md +41 -14
- package/skills/weaver-e10-yepiaotong/references/invoice-issuing-workflow.md +54 -26
- package/skills/weaver-e10-yepiaotong/references/invoice-personal-list.md +6 -5
- package/skills/weaver-e10-yepiaotong/references/invoice-reim.md +14 -11
- package/skills/weaver-e10-yepiaotong/references/reim-api-reference.md +97 -56
- package/skills/weaver-e10-yepiaotong/references/reim-examples.md +84 -60
- package/skills/weaver-e10-yepiaotong/references/reim-form-fill-rules.md +142 -105
- package/skills/weaver-e10-yepiaotong/references/reim-matching-rules.md +53 -194
- package/skills/weaver-e10-yepiaotong/references/reim-phone-subsidy-rules.md +7 -7
- package/skills/weaver-e10-yepiaotong/references/reim-workflow.md +164 -72
- package/skills/weaver-e10-yepiaotong/references/source-manifest.json +8 -2
- package/templates/meeting/invitation.docx +0 -0
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ⑧ 报价单生成合同:xlsx 解析 + docx 模板填充(零依赖 OOXML)。
|
|
3
|
+
*
|
|
4
|
+
* 语义与源脚本 quote_to_contract.py 对齐:
|
|
5
|
+
* - 硬门禁:没有真实 .docx 模板禁止生成合同,禁止把报价单直接渲染成合同。
|
|
6
|
+
* - sheet 由用户选择,禁止自动选(sheets 只列名)。
|
|
7
|
+
* - 分层打分:精确(100) > 去括号精确(98) > 别名是标签子串(70-95) > 标签是别名子串(60) > 字符重合(40);
|
|
8
|
+
* “大写/小写”硬约束避免“甲方”被“甲方联系人”反超。
|
|
9
|
+
* - 纵向合并:文本列可继承、数值列只归首行(防止 20 万被重复计成 200 万)。
|
|
10
|
+
* - 一致性校验:分节明细求和 vs 表内合计、明细总计 vs 标准价合计、折扣提示。
|
|
11
|
+
*/
|
|
12
|
+
import { basename, resolve } from 'node:path';
|
|
13
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
14
|
+
import { contractValidationError } from '../errors.js';
|
|
15
|
+
import { requireNonEmptyString } from './shared.js';
|
|
16
|
+
import { listSheetNames, readGrid } from '../ooxml/xlsx.js';
|
|
17
|
+
import { fillDocx, labelBefore } from '../ooxml/docx.js';
|
|
18
|
+
// ---------------------------------------------------------------- 人民币大写
|
|
19
|
+
const DIGITS = '零壹贰叁肆伍陆柒捌玖';
|
|
20
|
+
const UNITS = ['', '拾', '佰', '仟'];
|
|
21
|
+
const GROUPS = ['', '万', '亿', '万亿'];
|
|
22
|
+
export function rmbUpper(num) {
|
|
23
|
+
const s = String(num ?? '').replace(/,/gu, '').trim();
|
|
24
|
+
if (!s || !/^-?\d+(\.\d+)?$/.test(s))
|
|
25
|
+
return '';
|
|
26
|
+
const neg = s.startsWith('-');
|
|
27
|
+
const v = parseFloat(s);
|
|
28
|
+
const cents = Math.round(Math.abs(v) * 100);
|
|
29
|
+
const yuan = Math.floor(cents / 100);
|
|
30
|
+
const jiaoFen = cents % 100;
|
|
31
|
+
const jiao = Math.floor(jiaoFen / 10);
|
|
32
|
+
const fen = jiaoFen % 10;
|
|
33
|
+
const chunk4 = (g) => {
|
|
34
|
+
let out = '';
|
|
35
|
+
let zero = false;
|
|
36
|
+
for (let pos = 3; pos >= 0; pos--) {
|
|
37
|
+
const d = Math.floor(g / 10 ** pos) % 10;
|
|
38
|
+
if (d === 0) {
|
|
39
|
+
zero = true;
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
if (zero && out)
|
|
43
|
+
out += '零';
|
|
44
|
+
zero = false;
|
|
45
|
+
out += DIGITS[d] + UNITS[pos];
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return out;
|
|
49
|
+
};
|
|
50
|
+
const intPart = (n) => {
|
|
51
|
+
if (n === 0)
|
|
52
|
+
return '零';
|
|
53
|
+
const groups = [];
|
|
54
|
+
let x = n;
|
|
55
|
+
while (x > 0) {
|
|
56
|
+
groups.push(x % 10000);
|
|
57
|
+
x = Math.floor(x / 10000);
|
|
58
|
+
}
|
|
59
|
+
let res = '';
|
|
60
|
+
for (let i = groups.length - 1; i >= 0; i--) {
|
|
61
|
+
const g = groups[i];
|
|
62
|
+
if (g === 0) {
|
|
63
|
+
if (res && i > 0 && groups.slice(0, i).some((v) => v !== 0) && !res.endsWith('零'))
|
|
64
|
+
res += '零';
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (res && g < 1000 && !res.endsWith('零'))
|
|
68
|
+
res += '零';
|
|
69
|
+
res += chunk4(g) + GROUPS[i];
|
|
70
|
+
}
|
|
71
|
+
return res.replace(/^零+|零+$/gu, '') || '零';
|
|
72
|
+
};
|
|
73
|
+
let res = intPart(yuan) + '元';
|
|
74
|
+
if (jiao === 0 && fen === 0) {
|
|
75
|
+
res += '整';
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
if (jiao)
|
|
79
|
+
res += DIGITS[jiao] + '角';
|
|
80
|
+
else if (fen)
|
|
81
|
+
res += '零';
|
|
82
|
+
if (fen)
|
|
83
|
+
res += DIGITS[fen] + '分';
|
|
84
|
+
}
|
|
85
|
+
return (neg ? '负' : '') + res;
|
|
86
|
+
}
|
|
87
|
+
// ---------------------------------------------------------------- xlsx 报价单解析
|
|
88
|
+
const HEADER_ALIASES = {
|
|
89
|
+
no: ['编号', '序号'],
|
|
90
|
+
name: ['功能模块', '涉及功能模块', '授权许可项', '实施服务项', '报价内容', '名称'],
|
|
91
|
+
way: ['产品实现方式'],
|
|
92
|
+
desc: ['功能描述', '详细描述', '描述', '说明'],
|
|
93
|
+
price: ['单价'],
|
|
94
|
+
qty: ['数量'],
|
|
95
|
+
amount: ['金额'],
|
|
96
|
+
remark: ['备注'],
|
|
97
|
+
};
|
|
98
|
+
const SECTION_RE = /^([一二三四五六七八九十]+)\s*、\s*(.+)$/u;
|
|
99
|
+
function isNum(s) {
|
|
100
|
+
const t = s.replace(/,/gu, '');
|
|
101
|
+
return t !== '' && /^-?\d+(\.\d+)?$/u.test(t);
|
|
102
|
+
}
|
|
103
|
+
function toNum(s) {
|
|
104
|
+
const t = s.replace(/,/gu, '');
|
|
105
|
+
if (!t || !/^-?\d+(\.\d+)?$/u.test(t))
|
|
106
|
+
return null;
|
|
107
|
+
const f = parseFloat(t);
|
|
108
|
+
return Number.isInteger(f) ? f : f;
|
|
109
|
+
}
|
|
110
|
+
function fmtNum(v) {
|
|
111
|
+
if (v === null || v === undefined || v === '')
|
|
112
|
+
return '';
|
|
113
|
+
const s = String(v).replace(/[,¥¥]/gu, '').trim();
|
|
114
|
+
const f = Number(s);
|
|
115
|
+
if (!Number.isFinite(f))
|
|
116
|
+
return String(v);
|
|
117
|
+
return f === Math.trunc(f) ? f.toLocaleString('en-US') : f.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
|
118
|
+
}
|
|
119
|
+
export function parseQuote(buffer, sheetName, keepRemark = false, includeZero = false) {
|
|
120
|
+
const names = listSheetNames(buffer);
|
|
121
|
+
if (names.length === 0)
|
|
122
|
+
throw contractValidationError('xlsx_invalid', 'xlsx 中没有工作表。');
|
|
123
|
+
const sheet = sheetName ?? names[0];
|
|
124
|
+
const sheetIndex = names.indexOf(sheet);
|
|
125
|
+
if (sheetIndex < 0) {
|
|
126
|
+
throw contractValidationError('sheet_not_found', `sheet 不存在:${sheet};可选:${names.join('、')}`);
|
|
127
|
+
}
|
|
128
|
+
const grid = readGrid(buffer, sheetIndex);
|
|
129
|
+
const rowvals = (r, numeric = false) => {
|
|
130
|
+
const out = [];
|
|
131
|
+
for (let c = 1; c <= grid.maxCol; c++) {
|
|
132
|
+
const direct = grid.values.get(r)?.get(c);
|
|
133
|
+
let v = direct?.text ?? '';
|
|
134
|
+
if (v === '' && !numeric)
|
|
135
|
+
v = grid.textMerged.get(`${r}:${c}`)?.text ?? '';
|
|
136
|
+
out.push(v.replace(/\u00a0/gu, ' ').trim());
|
|
137
|
+
}
|
|
138
|
+
return out;
|
|
139
|
+
};
|
|
140
|
+
const numrow = (r) => {
|
|
141
|
+
const out = [];
|
|
142
|
+
for (let c = 1; c <= grid.maxCol; c++) {
|
|
143
|
+
const direct = grid.values.get(r)?.get(c);
|
|
144
|
+
let v = direct?.text ?? '';
|
|
145
|
+
// 数值列:纵向合并值只归首行——非首行一律视为空
|
|
146
|
+
if (grid.numMerged.has(`${r}:${c}`) && grid.textMerged.get(`${r}:${c}`) !== undefined) {
|
|
147
|
+
// 首行本身:numMerged 与 textMerged 都指向自身,直接用原值
|
|
148
|
+
}
|
|
149
|
+
else if (grid.textMerged.get(`${r}:${c}`) !== undefined) {
|
|
150
|
+
v = '';
|
|
151
|
+
}
|
|
152
|
+
out.push(v.replace(/\u00a0/gu, ' ').trim());
|
|
153
|
+
}
|
|
154
|
+
return out;
|
|
155
|
+
};
|
|
156
|
+
const rows = [];
|
|
157
|
+
const nrows = [];
|
|
158
|
+
for (let r = 1; r <= grid.maxRow; r++) {
|
|
159
|
+
rows.push(rowvals(r));
|
|
160
|
+
nrows.push(numrow(r));
|
|
161
|
+
}
|
|
162
|
+
const firstIdx = (row, keys) => {
|
|
163
|
+
for (let i = 0; i < row.length; i++)
|
|
164
|
+
if (keys.includes(row[i]))
|
|
165
|
+
return i;
|
|
166
|
+
return null;
|
|
167
|
+
};
|
|
168
|
+
// ---- 元信息
|
|
169
|
+
const meta = { customer: '', quote_date: '', sales: '', phone: '' };
|
|
170
|
+
for (const row of rows) {
|
|
171
|
+
const joined = row.join('');
|
|
172
|
+
for (const [label, key] of [['客户名称', 'customer'], ['报价日期', 'quote_date'], ['客户经理', 'sales'], ['联系电话', 'phone']]) {
|
|
173
|
+
if (!joined.includes(label) || meta[key])
|
|
174
|
+
continue;
|
|
175
|
+
const i = firstIdx(row, [label + ':', label + ':', label]);
|
|
176
|
+
if (i === null)
|
|
177
|
+
continue;
|
|
178
|
+
for (let j = i + 1; j < row.length; j++) {
|
|
179
|
+
const v = row[j];
|
|
180
|
+
if (v && v.replace(/[::]$/u, '') !== label && !['报价日期', '客户经理', '联系电话'].some((x) => v.startsWith(x))) {
|
|
181
|
+
meta[key] = v.replace(/[::]$/u, '');
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
// ---- 项目总价区
|
|
188
|
+
const totals = {};
|
|
189
|
+
for (const row of rows) {
|
|
190
|
+
for (const [label, key] of [['标准价合计', 'standard_total'], ['最终优惠报价', 'final_total']]) {
|
|
191
|
+
if (totals[key] !== undefined)
|
|
192
|
+
continue;
|
|
193
|
+
const i = firstIdx(row, [label + ':', label + ':', label]);
|
|
194
|
+
if (i === null)
|
|
195
|
+
continue;
|
|
196
|
+
for (let j = i + 1; j < row.length; j++) {
|
|
197
|
+
if (isNum(row[j])) {
|
|
198
|
+
totals[key] = toNum(row[j]);
|
|
199
|
+
totals[key + '_upper'] = rmbUpper(totals[key]);
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
// ---- 分节明细
|
|
206
|
+
const sections = [];
|
|
207
|
+
let cur = null;
|
|
208
|
+
let headerIdx = null;
|
|
209
|
+
for (let ri = 0; ri < rows.length; ri++) {
|
|
210
|
+
const row = rows[ri];
|
|
211
|
+
const nrow = nrows[ri];
|
|
212
|
+
let m = null;
|
|
213
|
+
for (const v of row) {
|
|
214
|
+
m = SECTION_RE.exec(v);
|
|
215
|
+
if (m)
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
if (m) {
|
|
219
|
+
const title = m[2].replace(/(单价[::].*?)|\(单价[::].*?\)/gu, '').trim();
|
|
220
|
+
cur = { code: m[1], title, items: [], total: null };
|
|
221
|
+
sections.push(cur);
|
|
222
|
+
headerIdx = null;
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
if (!cur)
|
|
226
|
+
continue;
|
|
227
|
+
if (row.some((v) => v.startsWith('合计'))) {
|
|
228
|
+
for (const v of row) {
|
|
229
|
+
if (isNum(v)) {
|
|
230
|
+
cur.total = toNum(v);
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
headerIdx = null;
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
if (!headerIdx) {
|
|
238
|
+
const ino = firstIdx(row, HEADER_ALIASES.no);
|
|
239
|
+
const iamt = firstIdx(row, HEADER_ALIASES.amount);
|
|
240
|
+
if (ino !== null && iamt !== null) {
|
|
241
|
+
headerIdx = Object.fromEntries(Object.entries(HEADER_ALIASES).map(([k, v]) => [k, firstIdx(row, v)]));
|
|
242
|
+
}
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
const g = (k) => {
|
|
246
|
+
const i = headerIdx[k];
|
|
247
|
+
return i !== null && i !== undefined && i < row.length ? row[i] : '';
|
|
248
|
+
};
|
|
249
|
+
const gn = (k) => {
|
|
250
|
+
const i = headerIdx[k];
|
|
251
|
+
return i !== null && i !== undefined && i < nrow.length ? nrow[i] : '';
|
|
252
|
+
};
|
|
253
|
+
const no = g('no');
|
|
254
|
+
const name = g('name');
|
|
255
|
+
const price = toNum(gn('price'));
|
|
256
|
+
const qty = toNum(gn('qty'));
|
|
257
|
+
let amount = toNum(gn('amount'));
|
|
258
|
+
if (!name && (amount === null || amount === undefined))
|
|
259
|
+
continue;
|
|
260
|
+
if ((amount === null || amount === undefined) && price !== null) {
|
|
261
|
+
amount = price * (qty !== null && qty !== undefined ? qty : 1);
|
|
262
|
+
}
|
|
263
|
+
if (!includeZero && (qty === 0 || amount === 0))
|
|
264
|
+
continue;
|
|
265
|
+
let desc = g('desc').replace(/\s+/gu, ' ').trim();
|
|
266
|
+
if (desc.length > 120)
|
|
267
|
+
desc = desc.slice(0, 120) + '…';
|
|
268
|
+
const item = { no, name, way: g('way'), desc, price, qty, amount };
|
|
269
|
+
if (keepRemark)
|
|
270
|
+
item.remark = g('remark').replace(/\s+/gu, ' ').slice(0, 200);
|
|
271
|
+
cur.items.push(item);
|
|
272
|
+
}
|
|
273
|
+
// ---- 一致性校验
|
|
274
|
+
const checks = [];
|
|
275
|
+
for (const s of sections) {
|
|
276
|
+
s.sum_items = s.items.reduce((acc, i) => acc + (Number(i.amount) || 0), 0);
|
|
277
|
+
if (s.total !== null && s.sum_items !== s.total) {
|
|
278
|
+
checks.push({ section: `${s.code}、${s.title}`, sheet_total: s.total, sum_items: s.sum_items, diff: s.sum_items - s.total });
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
const grand = sections.reduce((acc, s) => acc + (s.sum_items ?? 0), 0);
|
|
282
|
+
totals.sum_items_grand = grand;
|
|
283
|
+
const standard = totals.standard_total;
|
|
284
|
+
totals.has_mismatch = checks.length > 0 || (standard !== undefined && grand !== standard);
|
|
285
|
+
totals.diff_vs_standard = standard === undefined ? null : grand - standard;
|
|
286
|
+
const warnings = [];
|
|
287
|
+
if (checks.length) {
|
|
288
|
+
warnings.push('分节明细求和与表内『合计』不一致,请与报价人核对:' +
|
|
289
|
+
checks.map((c) => `${c.section} 表内${c.sheet_total}/明细${c.sum_items}`).join(';'));
|
|
290
|
+
}
|
|
291
|
+
if (totals.diff_vs_standard) {
|
|
292
|
+
warnings.push(`全部明细求和 ${grand} 与『标准价合计』${standard} 不一致,差额 ${totals.diff_vs_standard}`);
|
|
293
|
+
}
|
|
294
|
+
const final = totals.final_total;
|
|
295
|
+
if (final !== undefined && standard !== undefined && final !== standard) {
|
|
296
|
+
warnings.push(`存在折扣:标准价 ${standard} → 最终优惠报价 ${final},合同金额应以最终优惠报价为准`);
|
|
297
|
+
}
|
|
298
|
+
if (!meta.customer)
|
|
299
|
+
warnings.push('报价单未填写『客户名称』,签约主体必须由用户提供');
|
|
300
|
+
return { source_sheet: sheet, available_sheets: names, meta, totals, sections, checks, warnings };
|
|
301
|
+
}
|
|
302
|
+
// ---------------------------------------------------------------- docx 填充(字段打分)
|
|
303
|
+
const FIELD_ALIASES = {
|
|
304
|
+
party_a: ['甲方', '买方', '客户名称', '客户', '采购方', '需方', '购货方', '甲方名称', '买方名称'],
|
|
305
|
+
party_a_addr: ['甲方地址', '买方地址', '客户地址', '甲方住所', '注册地址'],
|
|
306
|
+
party_a_contact: ['甲方联系人', '买方联系人', '客户联系人', '项目联系人'],
|
|
307
|
+
party_a_phone: ['甲方电话', '买方电话', '联系电话', '客户电话', '甲方联系方式'],
|
|
308
|
+
party_b: ['乙方', '卖方', '供方', '销售方', '乙方名称', '卖方名称'],
|
|
309
|
+
party_b_addr: ['乙方地址', '卖方地址', '乙方住所'],
|
|
310
|
+
party_b_contact: ['乙方联系人', '卖方联系人', '客户经理', '销售经理'],
|
|
311
|
+
party_b_phone: ['乙方电话', '卖方电话'],
|
|
312
|
+
sign_date: ['签订日期', '签署日期', '签订时间', '合同日期', '日期'],
|
|
313
|
+
quote_date: ['报价日期', '报价时间'],
|
|
314
|
+
amount: ['合同总金额', '合同金额', '总金额', '合同总价', '项目总价', '金额合计', '合同总价款', '总价'],
|
|
315
|
+
amount_upper: ['大写金额', '金额大写', '人民币大写', '大写'],
|
|
316
|
+
software_fee: ['软件功能模块费用', '软件功能费用', '软件费用', '模块费用'],
|
|
317
|
+
license_fee: ['使用授权许可费用', '授权许可费用', '许可费用', '授权费用'],
|
|
318
|
+
license_qty: ['许可数量', '用户数', '授权用户数', '注册用户数'],
|
|
319
|
+
implement_fee: ['项目实施服务费用', '实施服务费用', '实施费用'],
|
|
320
|
+
ai_fee: ['合同AI扩展能力费用', 'AI扩展能力费用', 'AI能力费用', '智能助手费用'],
|
|
321
|
+
thirdparty_fee: ['第三方费用', '第三方产品费用'],
|
|
322
|
+
project_name: ['项目名称', '项目名称及内容', '合同名称'],
|
|
323
|
+
delivery: ['交付期限', '交付时间', '实施周期', '项目周期', '履行期限'],
|
|
324
|
+
payment: ['付款方式', '支付方式', '结算方式', '付款条件'],
|
|
325
|
+
sales: ['客户经理', '报价人', '销售代表'],
|
|
326
|
+
};
|
|
327
|
+
const NUMERIC_KEYS = new Set(['amount', 'software_fee', 'license_fee', 'license_qty', 'implement_fee', 'ai_fee', 'thirdparty_fee']);
|
|
328
|
+
function cleanLabel(label) {
|
|
329
|
+
let lab = label.replace(/\u00a0/gu, ' ').trim();
|
|
330
|
+
for (const ch of [':', ':', '(买方)', '(买方)', '(卖方)', '(卖方)'])
|
|
331
|
+
lab = lab.split(ch).join('');
|
|
332
|
+
return lab.replace(/[((]/gu, '').replace(/[))]/gu, '').trim();
|
|
333
|
+
}
|
|
334
|
+
export function scoreLabel(label, key) {
|
|
335
|
+
const lab = cleanLabel(label);
|
|
336
|
+
if (!lab)
|
|
337
|
+
return 0;
|
|
338
|
+
if (lab in FIELD_ALIASES)
|
|
339
|
+
return lab === key ? 100 : 0;
|
|
340
|
+
if (lab.includes('大写') && key !== 'amount_upper')
|
|
341
|
+
return 0;
|
|
342
|
+
if (lab.includes('小写') && key === 'amount_upper')
|
|
343
|
+
return 0;
|
|
344
|
+
const aliases = FIELD_ALIASES[key] ?? [];
|
|
345
|
+
if (aliases.includes(lab))
|
|
346
|
+
return 100;
|
|
347
|
+
for (const a of aliases) {
|
|
348
|
+
if (lab === a.replace(/[((]/gu, '').replace(/[))]/gu, ''))
|
|
349
|
+
return 98;
|
|
350
|
+
}
|
|
351
|
+
let best = 0;
|
|
352
|
+
for (const a of aliases) {
|
|
353
|
+
if (a.length >= 2 && lab.includes(a)) {
|
|
354
|
+
best = Math.max(best, Math.min(95, 70 + Math.floor((30 * a.length) / Math.max(lab.length, 1))));
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
for (const a of aliases) {
|
|
358
|
+
if (lab.length >= 3 && a.includes(lab))
|
|
359
|
+
best = Math.max(best, 60);
|
|
360
|
+
}
|
|
361
|
+
if (best)
|
|
362
|
+
return best;
|
|
363
|
+
for (const a of aliases) {
|
|
364
|
+
const common = [...new Set(a)].filter((ch) => lab.includes(ch)).length;
|
|
365
|
+
if (common >= 2)
|
|
366
|
+
best = Math.max(best, 40);
|
|
367
|
+
}
|
|
368
|
+
return best;
|
|
369
|
+
}
|
|
370
|
+
export function makeFiller(data) {
|
|
371
|
+
const valOf = (k) => {
|
|
372
|
+
const v = data[k];
|
|
373
|
+
if (v === null || v === undefined || v === '')
|
|
374
|
+
return '';
|
|
375
|
+
return NUMERIC_KEYS.has(k) ? fmtNum(v) : String(v);
|
|
376
|
+
};
|
|
377
|
+
return {
|
|
378
|
+
bestKey(label, floor) {
|
|
379
|
+
let bk = null;
|
|
380
|
+
let bs = 0;
|
|
381
|
+
for (const k of Object.keys(FIELD_ALIASES)) {
|
|
382
|
+
const s = scoreLabel(label, k);
|
|
383
|
+
if (s > bs) {
|
|
384
|
+
bk = k;
|
|
385
|
+
bs = s;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return bs >= floor ? { key: bk, score: bs } : { key: null, score: bs };
|
|
389
|
+
},
|
|
390
|
+
valOf,
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
// ---------------------------------------------------------------- 操作定义
|
|
394
|
+
function assertContractDocument(path) {
|
|
395
|
+
const absolute = resolve(path);
|
|
396
|
+
if (!/\.(docx)$/iu.test(absolute)) {
|
|
397
|
+
throw contractValidationError('bad_document', '只支持 .docx 模板;.doc 老格式请先另存为 .docx。');
|
|
398
|
+
}
|
|
399
|
+
return absolute;
|
|
400
|
+
}
|
|
401
|
+
export const contractQuoteOperations = [
|
|
402
|
+
{
|
|
403
|
+
name: 'contract.quote.rmb',
|
|
404
|
+
inputSchema: {
|
|
405
|
+
type: 'object',
|
|
406
|
+
additionalProperties: false,
|
|
407
|
+
properties: { num: { type: 'string' } },
|
|
408
|
+
required: ['num'],
|
|
409
|
+
},
|
|
410
|
+
async execute(input) {
|
|
411
|
+
const text = rmbUpper(requireNonEmptyString(input.num, 'num'));
|
|
412
|
+
if (!text)
|
|
413
|
+
throw contractValidationError('bad_number', '数值应为纯数字(可含千分位逗号)。');
|
|
414
|
+
return { status: 'DONE', data: { num: input.num, rmb_upper: text } };
|
|
415
|
+
},
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
name: 'contract.quote.sheets',
|
|
419
|
+
risk: 'read-with-upload',
|
|
420
|
+
inputSchema: {
|
|
421
|
+
type: 'object',
|
|
422
|
+
additionalProperties: false,
|
|
423
|
+
properties: { path: { type: 'string' } },
|
|
424
|
+
required: ['path'],
|
|
425
|
+
},
|
|
426
|
+
async execute(input) {
|
|
427
|
+
const path = resolve(String(input.path));
|
|
428
|
+
const sheets = listSheetNames(readFileSync(path));
|
|
429
|
+
if (!sheets.length)
|
|
430
|
+
throw contractValidationError('xlsx_invalid', 'xlsx 中没有工作表。');
|
|
431
|
+
return {
|
|
432
|
+
status: 'DONE',
|
|
433
|
+
data: { sheets, note: '把候选 sheet 列给用户选择;不要自动选第一个以外的 sheet。' },
|
|
434
|
+
};
|
|
435
|
+
},
|
|
436
|
+
},
|
|
437
|
+
{
|
|
438
|
+
name: 'contract.quote.parse',
|
|
439
|
+
risk: 'read-with-upload',
|
|
440
|
+
inputSchema: {
|
|
441
|
+
type: 'object',
|
|
442
|
+
additionalProperties: false,
|
|
443
|
+
properties: {
|
|
444
|
+
path: { type: 'string' },
|
|
445
|
+
sheet: { type: 'string' },
|
|
446
|
+
keepRemark: { type: 'boolean' },
|
|
447
|
+
includeZero: { type: 'boolean', description: '保留数量为0/金额0的未选购项' },
|
|
448
|
+
},
|
|
449
|
+
required: ['path'],
|
|
450
|
+
},
|
|
451
|
+
async execute(input) {
|
|
452
|
+
const path = resolve(String(input.path));
|
|
453
|
+
const parsed = parseQuote(readFileSync(path), input.sheet ? String(input.sheet) : null, Boolean(input.keepRemark), Boolean(input.includeZero));
|
|
454
|
+
return { status: 'DONE', data: parsed };
|
|
455
|
+
},
|
|
456
|
+
},
|
|
457
|
+
{
|
|
458
|
+
name: 'contract.quote.fill',
|
|
459
|
+
risk: 'read-with-upload',
|
|
460
|
+
inputSchema: {
|
|
461
|
+
type: 'object',
|
|
462
|
+
additionalProperties: false,
|
|
463
|
+
properties: {
|
|
464
|
+
template: { type: 'string', description: '真实合同模板 .docx 路径(必须)' },
|
|
465
|
+
data: { type: 'string', description: '数据 JSON 文件路径;含 __sections 时追加明细表' },
|
|
466
|
+
out: { type: 'string' },
|
|
467
|
+
detail: { type: 'string', enum: ['append', 'none'] },
|
|
468
|
+
mapOut: { type: 'string', description: '映射日志输出路径(filled/unfilled)' },
|
|
469
|
+
},
|
|
470
|
+
required: ['template', 'data', 'out'],
|
|
471
|
+
},
|
|
472
|
+
async execute(input) {
|
|
473
|
+
const template = assertContractDocument(String(input.template));
|
|
474
|
+
if (!existsSync(template)) {
|
|
475
|
+
throw contractValidationError('template_missing', '合同模板缺失:必须先取到真实的 .docx 模板才能生成合同。取不到就停止并请用户提供模板文件;禁止把报价单内容直接生成成一份合同。');
|
|
476
|
+
}
|
|
477
|
+
let data;
|
|
478
|
+
try {
|
|
479
|
+
data = JSON.parse(readFileSync(resolve(String(input.data)), 'utf8'));
|
|
480
|
+
}
|
|
481
|
+
catch (error) {
|
|
482
|
+
throw contractValidationError('bad_data', `数据 JSON 无法读取或解析:${error instanceof Error ? error.message : String(error)}`);
|
|
483
|
+
}
|
|
484
|
+
const detail = input.detail === 'none' ? 'none' : 'append';
|
|
485
|
+
const filler = makeFiller(data);
|
|
486
|
+
const { buffer, result } = fillDocx(template, filler, {
|
|
487
|
+
detail,
|
|
488
|
+
sections: (data['__sections'] ?? undefined),
|
|
489
|
+
amount: (data['amount'] ?? null),
|
|
490
|
+
});
|
|
491
|
+
const out = resolve(String(input.out));
|
|
492
|
+
writeFileSync(out, buffer);
|
|
493
|
+
const response = {
|
|
494
|
+
out,
|
|
495
|
+
template: basename(template),
|
|
496
|
+
filled_count: result.filled.length,
|
|
497
|
+
filled: result.filled,
|
|
498
|
+
unfilled: result.unfilled,
|
|
499
|
+
warning: result.unfilled.length > 0
|
|
500
|
+
? '存在未填充占位/空白:请把 unfilled 列表给用户确认,缺的信息让用户补充后再填,不要编造。'
|
|
501
|
+
: null,
|
|
502
|
+
};
|
|
503
|
+
if (input.mapOut) {
|
|
504
|
+
writeFileSync(resolve(String(input.mapOut)), JSON.stringify({ filled: result.filled, unfilled: result.unfilled }, null, 2), 'utf8');
|
|
505
|
+
response.map_out = resolve(String(input.mapOut));
|
|
506
|
+
}
|
|
507
|
+
return { status: 'DONE', data: response };
|
|
508
|
+
},
|
|
509
|
+
},
|
|
510
|
+
];
|
|
511
|
+
export function quoteContractContracts() {
|
|
512
|
+
return [
|
|
513
|
+
{
|
|
514
|
+
name: 'contract.quote.rmb',
|
|
515
|
+
summary: '数字转人民币大写(纯本地)',
|
|
516
|
+
risk: 'read',
|
|
517
|
+
inputSchema: {
|
|
518
|
+
type: 'object', additionalProperties: false,
|
|
519
|
+
properties: { num: { type: 'string' } }, required: ['num'],
|
|
520
|
+
},
|
|
521
|
+
},
|
|
522
|
+
{
|
|
523
|
+
name: 'contract.quote.sheets',
|
|
524
|
+
summary: '列出报价单 xlsx 的全部 sheet 名',
|
|
525
|
+
risk: 'read-with-upload',
|
|
526
|
+
inputSchema: {
|
|
527
|
+
type: 'object', additionalProperties: false,
|
|
528
|
+
properties: { path: { type: 'string' } }, required: ['path'],
|
|
529
|
+
},
|
|
530
|
+
},
|
|
531
|
+
{
|
|
532
|
+
name: 'contract.quote.parse',
|
|
533
|
+
summary: '解析报价单 xlsx:元信息/总价/分节明细/一致性校验',
|
|
534
|
+
risk: 'read-with-upload',
|
|
535
|
+
inputSchema: {
|
|
536
|
+
type: 'object', additionalProperties: false,
|
|
537
|
+
properties: {
|
|
538
|
+
path: { type: 'string' }, sheet: { type: 'string' },
|
|
539
|
+
keepRemark: { type: 'boolean' }, includeZero: { type: 'boolean' },
|
|
540
|
+
}, required: ['path'],
|
|
541
|
+
},
|
|
542
|
+
},
|
|
543
|
+
{
|
|
544
|
+
name: 'contract.quote.fill',
|
|
545
|
+
summary: '用真实 .docx 模板 + 数据 JSON 生成合同 docx',
|
|
546
|
+
risk: 'read-with-upload',
|
|
547
|
+
inputSchema: {
|
|
548
|
+
type: 'object', additionalProperties: false,
|
|
549
|
+
properties: {
|
|
550
|
+
template: { type: 'string' }, data: { type: 'string' }, out: { type: 'string' },
|
|
551
|
+
detail: { type: 'string', enum: ['append', 'none'] }, mapOut: { type: 'string' },
|
|
552
|
+
}, required: ['template', 'data', 'out'],
|
|
553
|
+
},
|
|
554
|
+
},
|
|
555
|
+
];
|
|
556
|
+
}
|
|
557
|
+
export { labelBefore };
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { contractPolicyError } from '../errors.js';
|
|
2
|
+
import { recommendPageUrl } from './page.js';
|
|
3
|
+
import { requireNonEmptyString } from './shared.js';
|
|
4
|
+
/** 「合同范文管理」对象的固定 obj_id,不是环境占位符,禁止替换。 */
|
|
5
|
+
export const RECOMMEND_TEXT_OBJ_ID = '1290191829260230657';
|
|
6
|
+
function text(value) {
|
|
7
|
+
if (typeof value === 'string' && value.trim())
|
|
8
|
+
return value;
|
|
9
|
+
if (typeof value === 'number' && Number.isFinite(value))
|
|
10
|
+
return String(value);
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
function mapRecommendation(item) {
|
|
14
|
+
const labels = (item.extract_labels ?? {});
|
|
15
|
+
return {
|
|
16
|
+
rank: item.rank,
|
|
17
|
+
fileName: item.file_name,
|
|
18
|
+
matchScore: item.match_score,
|
|
19
|
+
matchReason: item.match_reason,
|
|
20
|
+
matchedFields: item.matched_fields,
|
|
21
|
+
templateId: text(item.template_id) ?? text(item.id),
|
|
22
|
+
docId: text(item.doc_id),
|
|
23
|
+
files: item.file_id,
|
|
24
|
+
classificationId: text(item.classification_id),
|
|
25
|
+
creator: item.creator,
|
|
26
|
+
updater: item.updater,
|
|
27
|
+
createTime: item.create_time,
|
|
28
|
+
updateTime: item.update_time,
|
|
29
|
+
labels: {
|
|
30
|
+
title: labels.title,
|
|
31
|
+
summary: labels.summary,
|
|
32
|
+
contractType: labels.contract_type,
|
|
33
|
+
riskLevel: labels.risk_level,
|
|
34
|
+
amountRange: labels.amount_range,
|
|
35
|
+
industry: labels.industry,
|
|
36
|
+
scene: labels.scene,
|
|
37
|
+
role: labels.role,
|
|
38
|
+
paymentTerms: labels.payment_terms,
|
|
39
|
+
keyClauses: labels.key_clauses,
|
|
40
|
+
jurisdiction: labels.jurisdiction,
|
|
41
|
+
tags: labels.tags,
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
export const contractRecommendOperations = [
|
|
46
|
+
{
|
|
47
|
+
name: 'contract.recommend.doc',
|
|
48
|
+
async execute(input, { host }) {
|
|
49
|
+
const content = requireNonEmptyString(input.content, 'content', 4000);
|
|
50
|
+
const value = await host.contractRequest({
|
|
51
|
+
method: 'POST',
|
|
52
|
+
path: '/api/ebd/contract/file/recommendContractDoc',
|
|
53
|
+
body: { content, textObjId: RECOMMEND_TEXT_OBJ_ID },
|
|
54
|
+
// 服务端 AI 语义匹配本身需要 30~40 秒,超时放到 180 秒,不要因慢而重试。
|
|
55
|
+
timeoutMs: 180_000,
|
|
56
|
+
});
|
|
57
|
+
const data = (value?.data ?? {});
|
|
58
|
+
const cacheKey = text(data.cacheKey);
|
|
59
|
+
if (!cacheKey) {
|
|
60
|
+
throw contractPolicyError('recommend_cache_key_missing', '推荐接口未返回 cacheKey,无法打开推荐结果页', {
|
|
61
|
+
path: '/api/ebd/contract/file/recommendContractDoc',
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
const list = Array.isArray(data.recommendList) ? data.recommendList : [];
|
|
65
|
+
const session = await host.getSession();
|
|
66
|
+
return {
|
|
67
|
+
status: list.length ? 'COMPLETE' : 'EMPTY',
|
|
68
|
+
data: {
|
|
69
|
+
cacheKey,
|
|
70
|
+
total: list.length,
|
|
71
|
+
recommendations: list.map(mapRecommendation),
|
|
72
|
+
url: recommendPageUrl(session.baseUrl, cacheKey),
|
|
73
|
+
page: { width: 1000 },
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
];
|
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
import { contractValidationError } from '../errors.js';
|
|
2
2
|
import { contractOperations } from '../manifest.js';
|
|
3
|
+
import { contractAssistantOperations } from './assistant.js';
|
|
4
|
+
import { contractClauseLibraryOperations } from './clause-library.js';
|
|
3
5
|
import { contractClauseReferenceOperations } from './clause-reference.js';
|
|
6
|
+
import { contractCompareOperations } from './compare.js';
|
|
7
|
+
import { contractCreditPageOperations } from './credit-page.js';
|
|
8
|
+
import { contractFlowOperations } from './flow.js';
|
|
9
|
+
import { contractPerformanceOperations } from './performance.js';
|
|
10
|
+
import { contractQuoteOperations } from './quote.js';
|
|
11
|
+
import { contractRecommendOperations } from './recommend.js';
|
|
4
12
|
import { contractRequestOperations } from './request.js';
|
|
13
|
+
import { contractReviewOperations } from './review.js';
|
|
5
14
|
import { contractSubjectOperations } from './subject.js';
|
|
6
15
|
import { contractTemplateOperations } from './template.js';
|
|
7
16
|
import { contractWorkflowOperations } from './workflow.js';
|
|
@@ -9,10 +18,19 @@ function buildRegistry() {
|
|
|
9
18
|
const registry = new Map();
|
|
10
19
|
const handlers = [
|
|
11
20
|
...contractTemplateOperations,
|
|
21
|
+
...contractClauseLibraryOperations,
|
|
12
22
|
...contractClauseReferenceOperations,
|
|
13
23
|
...contractSubjectOperations,
|
|
14
24
|
...contractWorkflowOperations,
|
|
15
25
|
...contractRequestOperations,
|
|
26
|
+
...contractReviewOperations,
|
|
27
|
+
...contractFlowOperations,
|
|
28
|
+
...contractRecommendOperations,
|
|
29
|
+
...contractCompareOperations,
|
|
30
|
+
...contractCreditPageOperations,
|
|
31
|
+
...contractAssistantOperations,
|
|
32
|
+
...contractPerformanceOperations,
|
|
33
|
+
...contractQuoteOperations,
|
|
16
34
|
];
|
|
17
35
|
for (const handler of handlers) {
|
|
18
36
|
if (registry.has(handler.name))
|